框架源碼系列九:依賴註入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配置方式的註冊和實例化過程