框架源码系列九:依赖注入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 ...
随机推荐
- error: RPC failed
error: RPC failed error: RPC failed; curl 56 GnuTLS recv error (-54): Error in the pull function. fa ...
- BeanPostProcessor出现init方法无法被调用Invocation of init method failed
是因为 返回了null,要返回object即可,arg0是bean对象本身,arg1是bean名字,即bean的id
- JWTtoken的原理以及在django中的应用
JWT 在用户注册或者登陆完成之后,记录用户状态,或者为用户创建身份凭证(功能类似于session的作用). 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而 ...
- JDK提供的几种线程池比较
JDK提供的几种线程池 newFixedThreadPool创建一个指定工作线程数量的线程池.每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中. ...
- UltralEdit 替换回车换行符
打开 Ue 工具,写下内容,如下图: 然后按 Ctrl + r,输入 ^p,点击按钮 “全部替换”, 如下图:
- Aizu2249 Road Construction(dijkstra优化+思路 好题)
https://vjudge.net/problem/Aizu-2249 感觉这题和2017女生赛的Deleting Edge思路很像,都是先找最短路,然后替换边的. 但是这题用最朴素的dijkstr ...
- USE " cc.exports.* = value " INSTEAD OF SET GLOBAL VARIABLE"
Cocos2d-x 3.5的lua项目生成后,变成了MVC模式,并且,加入了一个全局变量的检测功能.也就是说,你不小心用了全局变量,他会提示你出错! 比如 local temp = 1 temp = ...
- Class:DbConnectionManipulator.cs
ylbtech-Class:DbConnectionManipulator.cs 1.返回顶部 1.DbConnectionManipulator.cs using System; using Sys ...
- Canvas 和 SVG 的不同
Canvas 和 SVG 都允许您在浏览器中创建图形,但是它们在根本上是不同的. SVG SVG 是一种使用 XML 描述 2D 图形的语言. SVG 基于 XML,这意味着 SVG DOM 中的每个 ...
- 【JVM】垃圾收集器
程序计数器.Java虚拟机栈.本地方法栈分配的内存是确定的,生命周期与线程同样.所以不须要过多考虑回收问题. 而Java堆和方法区仅仅有运行时才知道有哪些对象被创建,须要多少内存,这部分的内存分配和回 ...