框架源码系列九:依赖注入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方式各注解的解析发生在哪