框架源码系列九:依赖注入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 ...
随机推荐
- koa2框架设置响应和请求头
https://koa.bootcss.com/#response 请耐心翻到网页下端,可以看到 设置响应头: ctx.set('Content-Type', 'application/zip') 添 ...
- Spring MVC ,使用mvc:resources标签后,处理器无法被访问
在SpringMVC的配置文件中添加了<mvc:resources mapping="/img/**" location="/img/"/>以便处理 ...
- npm install出现"Unexpected end of JSON input while parsing near"
打开命令行输入 npm cache clean --force 重新npm i,即可解决报错
- LINUX文件及目录管理命令基础(2)
Linux文件类型 文件作为Linux操作系统中最常见的对象,在系统中被分为了多种类型 如下: - 普通文件 d 目录 l 链接 b 块设备 c 字符设备 p 管道设备 s 套接字文件 Linux目录 ...
- 游戏UI规范
在满足效果的前提下,尽量做到UI资源做到复用和最小化 1. 背景1和背景2分开切,可以组合成各种不同的面包背景图 2. 背景1和背景2在没有花纹的情况下,中间纯色的部分切4个像素做就公共个缩放就可 ...
- 3ds max学习笔记(十一)-- 修改器
1.修改器列表: 将常用的修改器放放置在集里: 1.点击[配置]按钮,勾选[显示按钮] 选择[配置修改器集],在新出的弹窗里通过左右拖拽进行设置: 2,选择之后,点击[确定]进行保存:
- GMA Round 1 抛硬币
传送门 抛硬币 扔一个硬币,正面概率为0.6.扔这枚硬币666次,正面就得3分,反面就得1分,求总分的方差. 直接套公式$np(1-p)*(X-Y)^2=666*0.6*(1-0.6)*(3-1)^2 ...
- golang 无法将Slice类型[]a 转换为 Slice[]b
因为我想做一个通用的Slice方法,AnySlice,但是将AnySlice定义为[]interface{ } 转换到别的类型,或者相互转换的时候都是会报错. 这是golang比较恶心人的一个地方了, ...
- js实现图片旋转
1.以下代码适用ie9版本 js代码如下: function rotate(o,p){ var img = document.getElementById(o); if(!img || !p) ret ...
- shell编程学习笔记(十一):Shell中的while/until循环
shell中也可以实现类似java的while循环 while循环是指满足条件时,进行循环 示例: #! /bin/sh index=10 while [ $index -gt 0 ] do inde ...