框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程
一、依赖注入DI
学习目标
1)搞清楚构造参数依赖注入的过程及类
2)搞清楚注解方式的属性依赖注入在哪里完成的。
学习思路
1)思考我们手写时是如何做的
2)读 spring 源码对比看它的实现
3)Spring 源码解读
1. 构造参数依赖注入
org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(String, RootBeanDefinition, Constructor<?>[], Object[])
->
org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(String, RootBeanDefinition, BeanWrapper, ConstructorArgumentValues, ConstructorArgumentValues)
、、

->
org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(Object, Object)





解析构造参数源代码的使用示例:
<bean id="combatService" factory-bean="loveServiceFactory"
factory-method="getCombatServiceFromMemberFactoryMethod" >
<constructor-arg type="int" value="120" />
<constructor-arg ref="beanA"></constructor-arg>
<constructor-arg><bean id="ssss" class="cccc"></bean></constructor-arg>
<constructor-arg><bean class="cccc"></bean></constructor-arg>
<constructor-arg><map></map></constructor-arg>
<constructor-arg><list></list></constructor-arg>
</bean>
拓展:初始化前初始化后处理
实现BeanPostProcessor,然后在里面的初始化前和初始化后的方法里面打断点看调用栈就可以找到初始化前和初始化后在哪里处理的了
实现BeanPostProcessor:
package com.study.leesmall.spring.ext; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component; @Component
public class MyBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("------MyBeanPostProcessor.postProcessBeforeInitialization for " + beanName);
return bean;
} public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("------MyBeanPostProcessor.postProcessAfterInitialization for " + beanName);
return bean;
}
}
在初始化前方法MyBeanPostProcessor.postProcessBeforeInitialization和MyBeanPostProcessor.postProcessAfterInitialization里面打断点拿到调用栈:
初始化前调用栈:

具体的初始化前处理:

初始化后调用栈:

具体的初始化后处理:

2、属性依赖注入
写个测试类里面含有get和set方法,然后在set方法里面打个断点拿到调用栈去分析
测试类:
import org.springframework.stereotype.Component; @Component
public class Bean3 {
private String value; public String getValue() {
return value;
} public void setValue(String value) {
this.value = value;
}
}
在xml里面的配置:
<bean id="bean3" class="com.study.leesmall.spring.sample.di.Bean3" scope="prototype" >
<property name="value" value="10"></property>
</bean>
测试入口:
package com.study.leesmall.spring.sample.di; import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext; public class DiMain { public static void main(String[] args) {
ApplicationContext context = new GenericXmlApplicationContext(
"classpath:com/study/leesmall/spring/sample/di/application.xml"); Bean3 b3 = context.getBean(Bean3.class);
}
}
在set方法里面打个断点拿到调用栈:

属性依赖值处理源码分析:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(String, RootBeanDefinition, BeanWrapper)


3. 属性循环依赖怎么处理
构成参数是不允许循环依赖的,属性是允许循环依赖的
1)示例代码
Bean1:
package com.study.leesmall.spring.sample.di; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; public class Bean1 { @Autowired
private Bean2 b2; public void do1() {
System.out.println("------------------do1");
} public Bean2 getB2() {
return b2;
} public void setB2(Bean2 b2) {
this.b2 = b2;
}
}
Bean2:
package com.study.leesmall.spring.sample.di;
import org.springframework.stereotype.Component;
public class Bean2 {
@Autowired
private Bean1 b1;
public void do2() {
b1.do1();
}
public Bean1 getB1() {
return b1;
}
public void setB1(Bean1 b1) {
this.b1 = b1;
}
}
/spring-source-study/src/main/java/com/study/leesmall/spring/sample/di/application.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bean1" class="com.study.leesmall.spring.sample.di.Bean1" >
<property name="b2" ref="bean2"></property>
</bean> <bean id="bean2" class="com.study.leesmall.spring.sample.di.Bean2" >
<property name="b1" ref="bean1"></property>
</bean> </beans>
测试类DiMain:
package com.study.leesmall.spring.sample.di; import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext; public class DiMain { public static void main(String[] args) {
ApplicationContext context = new GenericXmlApplicationContext(
"classpath:com/study/leesmall/spring/sample/di/application.xml"); Bean2 b2 = context.getBean(Bean2.class);
b2.do2();
}
}
2)请分别尝试在xml配置中配置如下三种情况,看是否可以循环依赖成功
a)Bean1和Bean2都是单例bean。
循环依赖成功
b)Bean1和Bean2一个是单例的,一个是原型的。
循环依赖成功
c)Bean1和Bean2都是原型的
循环依赖不成功
3)思考:为什么两个是原型时不能循环依赖,而单例时可以?
一个Bean的属性依赖另一个bean实例,注入的过程是否就是从BeanFactory中getBean(),再赋值给属性?
是,首先从Bean工厂里面获取依赖的bean,没有就创建
那为什么单例可以,原型不可以?
依据上面的逻辑,那就单例时可以getBean()获得依赖的bean实例,原型时不能,为什么?
再来回想一下单例bean和原型bean在BeanFactory中的区别:
单例Bean是缓存在BeanFactory中的,而原型Bean是不缓存的。
缓存和不缓存对于循环依赖的处理有什么不同呢?
思考一下创建Bean实例的过程:
先Bean1,创建Bean1的实例——>然后对其属性进行依赖注入处理——>依赖Bean2,从BeanFactorygetBean("bean2")——>创建Bean2的实例——>对Bean2实例的属性进行依赖注入处理——>依赖Bean1,从BeanFactory中获取Bean1的实例
缓存的就可以通过beanFactory的getBean()获得前一个Bean的实例。而如果不缓存的,则bean2实例依赖注入Bean1时,从BeanFactorygetBean()就会又创建一个Bean1的实例,如此会无限循环依赖创建下去。
再仔细想一下,对于单例bean的缓存有时机的要求吗?
有,一定要在进行属性依赖注入处理前缓存(暴露)到BeanFactory中。
4)看源码找到单例Bean暴露(缓存)到BeanFactory中的代码,它应在创建Bean实例后,进行属性依赖注入前
在AbstractAutowireCapableBeanFactory.doCreateBean()方法中
// 为循环引用依赖提前缓存单例 bean
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// 是否提早暴露单例 Bean 实例
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace(
"Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
5)看一遍属性注入的代码逻辑验证getBean(),单例的获取
代码在AbstractAutowireCapableBeanFactory.populateBean()中。
Xml配置方式的处理逻辑在方法最后的applyPropertyValues(beanName,mbd,bw,pvs);方法中。
注解的方式则在之上的InstantiationAwareBeanPostProcessor执行中:
if (hasInstAwareBpps) {
if (pvs == null) {
pvs = mbd.getPropertyValues();
}
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
}
}
二、三种 Bean 配置方式的注册、实例化过程
1. Xml
BeanF:
package com.study.leesmall.spring.sample.config;
import org.springframework.beans.factory.annotation.Autowired;
public class BeanF {
@Autowired
private BeanE be;
public void do1() {
System.out.println("----------" + this + " do1");
this.be.doSomething();
}
}
BeanE:
package com.study.leesmall.spring.sample.config;
public class BeanE {
public void doSomething() {
System.out.println("-----" + this + " doSomething ");
}
}
xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="beanE" class="com.study.leesmall.spring.sample.config.BeanE" /> <bean id="beanF" class="com.study.leesmall.spring.sample.config.BeanF" ></bean> <context:annotation-config/> <context:component-scan base-package="com.study.leesmall.spring.sample.config" ></context:component-scan> </beans>
测试类:
XMLConfigMain
package com.study.leesmall.spring.sample.config; import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext; public class XMLConfigMain { public static void main(String[] args) {
ApplicationContext context = new GenericXmlApplicationContext(
"classpath:com/study/leesmall/spring/sample/config/application.xml"); BeanF bf = context.getBean(BeanF.class);
bf.do1(); } }
测试类运行结果:
----------com.study.leesmall.spring.sample.config.BeanF@19d37183 do1
-----com.study.leesmall.spring.sample.config.BeanE@1a0dcaa doSomething
2. Annotation 注解
BeanG
package com.study.leesmall.spring.sample.config; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Component; @Component
@ImportResource("classpath:com/study/leesmall/spring/sample/config/application.xml")
public class BeanG { @Autowired
private BeanF beanf; public void dog() {
System.out.println("----------------------------------------");
this.beanf.do1();
}
}
在xml里面开启注解扫描:
<context:annotation-config/>
<context:component-scan base-package="com.study.leesmall.spring.sample.config" ></context:component-scan>
测试类:
AnnotationMain
package com.study.leesmall.spring.sample.config; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class AnnotationMain { public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext("com.study.leesmall.spring.sample.config"); BeanG bg = context.getBean(BeanG.class);
bg.dog();
}
}
测试结果:
----------------------------------------
----------com.study.leesmall.spring.sample.config.BeanF@6f204a1a do1
-----com.study.leesmall.spring.sample.config.BeanE@2de56eb2 doSomething
3. Java based
BeanH:
package com.study.leesmall.spring.sample.config;
import org.springframework.beans.factory.annotation.Autowired;
public class BeanH {
@Autowired
private BeanE be;
public void doH() {
System.out.println("-----------" + this + " doH");
be.doSomething();
}
}
测试类:
JavaBasedMain
package com.study.leesmall.spring.sample.config; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; @Configuration
@ComponentScan("com.study.leesmall.spring.sample.config")
public class JavaBasedMain { @Bean
public BeanH getBeanH() {
return new BeanH();
} public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(JavaBasedMain.class); BeanH bh = context.getBean(BeanH.class);
bh.doH();
}
}
测试结果:
-----------com.study.leesmall.spring.sample.config.BeanH@475e586c doH
-----com.study.leesmall.spring.sample.config.BeanE@657c8ad9 doSomething
问题:
1、 xml 方式中怎么开启注解支持?
在application.xml里面加入如下配置:
<context:annotation-config/>
<context:component-scan base-package="com.study.leesmall.spring.sample.config" ></context:component-scan>
2、xml方式中开启注解支持,是如何实现的?该怎么去看?你会怎么实现?
入口在Spring的E:\repository\org\springframework\spring-context\5.1.3.RELEASE\spring-context-5.1.3.RELEASE.jar /META-INF/spring.handlers里面
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
ContextNamespaceHandler就是入口:

类org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser就是用来解析下面的配置的:
<context:annotation-config/>

注册注解配置处理器的方法org.springframework.context.annotation.AnnotationConfigUtils.registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)

Autowired注解的处理属性注入的方法:
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(PropertyValues, Object, String)


org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement.inject(Object, String, PropertyValues):

3、Xml中的<context:component-scan>是如何实现的?
4、注解方式可以嵌入xml吗?
5、Javabase方式各注解的解析发生在哪
框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程的更多相关文章
- 小白都能看懂的 Spring 源码揭秘之依赖注入(DI)源码分析
目录 前言 依赖注入的入口方法 依赖注入流程分析 AbstractBeanFactory#getBean AbstractBeanFactory#doGetBean AbstractAutowireC ...
- spring的ioc依赖注入的三种方法(xml方式)
常见的依赖注入方法有三种:构造函数注入.set方法注入.使用P名称空间注入数据.另外说明下注入集合属性 先来说下最常用的那个注入方法吧. 一.set方法注入 顾名思义,就是在类中提供需要注入成员的 s ...
- 3.2spring源码系列----循环依赖源码分析
首先,我们在3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 中手写了循环依赖的实现. 这个实现就是模拟的spring的循环依赖. 目的是为了更容易理解spring源码 ...
- 3.4 spring5源码系列--循环依赖的设计思想
前面已经写了关于三篇循环依赖的文章, 这是一个总结篇 第一篇: 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 第二篇: 3.2spring源码系列----循环依赖源 ...
- ASP.NET MVC中使用Unity进行依赖注入的三种方式
在ASP.NET MVC中使用Unity进行依赖注入的三种方式 2013-12-15 21:07 by 小白哥哥, 146 阅读, 0 评论, 收藏, 编辑 在ASP.NET MVC4中,为了在解开C ...
- React 源码中的依赖注入方法
一.前言 依赖注入(Dependency Injection)这个概念的兴起已经有很长时间了,把这个概念融入到框架中达到出神入化境地的,非Spring莫属.然而在前端领域,似乎很少会提到这个概念,难道 ...
- 【Spring源码解析】—— 依赖注入结合SpringMVC Demo-xml配置理解
在IOC容器初始化的梳理之后,对依赖注入做一个总结,就是bean实例化的过程,bean的定义有两种方式,一种是xml文件配置,一种是注解,这里是对xml配置文件的依赖注入的介绍,后续对bean与该部分 ...
- Spring注解依赖注入的三种方式的优缺点以及优先选择
当我们在使用依赖注入的时候,通常有三种方式: 1.通过构造器来注入: 2.通过setter方法来注入: 3.通过filed变量来注入: 那么他们有什么区别吗?应该选择哪种方式更好? 三种方式的区别小结 ...
- 框架源码系列二:手写Spring-IOC和Spring-DI(IOC分析、IOC设计实现、DI分析、DI实现)
一.IOC分析 1. IOC是什么? IOC:Inversion of Control控制反转,也称依赖倒置(反转) 问题:如何理解控制反转? 反转:依赖对象的获得被反转了.由自己创建,反转为从IOC ...
随机推荐
- 读《31天学会CRM项目开发》记录2 - 企业信息管理系统
在信息技术的快速推动下,企业如果依然利用传统的管理方式,以人为主,那效率便会大打折扣.在此背景下,企业信息化系统得 到了高速发展.如我们常见的ERP系统.MES系统,都是提高公司运行效率,降低运营以及 ...
- [P1516]青蛙的约会 (扩展欧几里得/中国剩余定理?)
每日做智推~ 一看就是一道数学题. 再看是一道公约数的题目. 标签是中国孙子定理. 题解是扩展欧几里得 (笑) 一开始没看数据范围 只有50分 开一个longlong就可以了 #include< ...
- C++程序设计方法1:分离代码
使用头文件,分离声明与定义 防止重复包含: #ifndef FUNC_H #define FUNC_H .... #endif main函数的命令行参数: #include <iostream& ...
- Code signing is required for product type 'Application' in SDK 'iOS 11.4'
查看链接: https://blog.csdn.net/Fantasy_Jun/article/details/78082359 处理方法: 将code signing identity设置为iOS ...
- 【原】通过AOP实现MyBatis多数据源的动态切换
[环境参数]1.开发框架:Spring + SpringMVC + MyBatis 2.数据库A的URL:jdbc.url=jdbc:mysql://172.16.17.164:3306/ test? ...
- js文档节点关系
这一节简单介绍一下文档节点方面的相关内容: 一:获得节点关系的属性 1.获得该元素父节点的引用:obj.parentNode: <div class="content"> ...
- Vue 开源项目库汇总(转)
最近做了一个Vue开源项目库汇总,里面集合了OpenDigg 上的优质的Vue开源项目库,方便移动开发人员便捷的找到自己需要的项目工具等,感兴趣的可以到GitHub上给个star.https://gi ...
- linux 下令chmod 755的意思
linux 命令chmod 755的意思 chmod是Linux下设置文件权限的命令,后面的数字表示不同用户或用户组的权限. 一般是三个数字:第一个数字表示文件所有者的权限第二个数字表示与文件所有者同 ...
- 如何修改maven的默认jdk版本
问题: 1.创建maven项目的时候,jdk版本是1.5版本,而自己安装的是1.7或者1.8版本. 2.每次右键项目名-maven->update project 时候,项目jdk版本变了,变回 ...
- golang time打印出的值是62135596800的来源
' 减去62135596800是将"以公元1年1月1日0点为基准"改成"以1970年1月1日0点"为基准 所以,数据库datetime的默认值 : 0000-0 ...