最近有空總結一下之前在使用spring boot時遇到過的幾種依賴注入時的坑,如果不了解spring內部的處理過程,使用起來總是感覺有種迷糊。
在分析場景前,需要大概了解一下spring對于bean的實例化過程是需要先注冊BeanDefinition信息然后才進行實例化,在org.springframework.context.support.AbstractApplicationContext#refresh中定義的基本的流程。部分代碼
try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // 1. 包含了BeanDefinition注冊過程 invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // 2. 根據BeanDefinition處理Bean實例化過程 finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); }
@Autowired依賴注入問題C邏輯使用先于@Autowired注解處理
之前不熟悉spring bean的實例化過程可能會遇到的坑就是使用@Autowired依賴注入的對象是null沒有注入到相應的對象里面,或者準確的來說是在我程序的某一塊邏輯代碼執行時使用到@Autowired依賴的bean,但是bean確實null。
這種場景一般就是在當時,@Autowired注解并沒有被處理,所以依賴的bean為null。
如果了解spring boot自動化裝配可以直達我們通過實現ImportBeanDefinitionRegistrar接口自定義注冊BeanDefinition信息,實現邏輯是ImportBeanDefinitionRegistrar#registerBeanDefinitions的具體實現中,這個方法的執行過程屬于
?// 1. 包含了BeanDefinition注冊過程 invokeBeanFactoryPostProcessors(beanFactory);
之前曾經遇到過在自定義的ImportBeanDefinitionRegistrar實現類中,使用@Autowired依賴某個bean,但是在使用時無法得到具體的實現對象,因為@Autowired注解的處理過程是在
?//2. 根據BeanDefinition處理Bean實例化過程 ?finihBeanFactoryInitialization(beanFactory);
當程序執行ImportBeanDefinitionRegistrar#registerBeanDefinitions時,依賴的bean為null,報空指針。
測試用例
引導程序代碼
@SpringBootApplication(scanBasePackages = "garine.learn.test.auwired") @Import(TestAutowiredRegistar.class) public class BootstrapTestApplication3 { public static void main(String[] args) { ApplicationContext applicationContext = SpringApplication.run(BootstrapTestApplication3.class, args); } }
@Component public class TestAutowiredRegistar implements ImportBeanDefinitionRegistrar,BeanFactoryAware,InitializingBean{ private BeanFactory beanFactory; @Autowired TestRegistarDependOn testRegistarDependOn; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { System.out.println(TestAutowiredRegistar.class.getSimpleName() + "-----" +testRegistarDependOn); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public void afterPropertiesSet() throws Exception { System.out.println(TestAutowiredRegistar.class.getSimpleName() + "-----" + testRegistarDependOn); } }
@Component public class TestRegistarDependOn { }
執行輸出:TestAutowiredRegistar-----null
**這種場景一般就是在當時,@Autowired注解并沒有被處理,所以依賴的bean為null。**如果遇到依賴注入為空時,如果確定已經定義了對應的bean,那么不妨看看代碼使用依賴bean時,到底@Autowired注解有沒有被處理。
這種場景的解決辦法就是使用BeanFactory來獲取bean,修改代碼如下。
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { System.out.println(TestAutowiredRegistar.class.getSimpleName() + "-----" +testRegistarDependOn); System.out.println(TestAutowiredRegistar.class.getSimpleName() + "-----" +beanFactory.getBean(TestRegistarDependOn.class)); }
再次執行輸出:
TestAutowiredRegistar-----null
TestAutowiredRegistar-----garine.learn.test.auwired.TestRegistarDependOn@7808fb9
BeanFactory.getBean問題CgetBean調用先于BeanDefinition信息注冊
這里就是beanFactory.getBean方法如果獲取不到bean就會調用bean的初始化過程。但是需要注意bean對應的BeanDefinition信息必須已經注冊完成。所以這種getBean的方式不是絕對安全。
一般而言ConfigurationClassParser#processConfigurationClass為入口,可以看到整個對Configclass的處理過程。對于@Configuration標注的類都是有排序的,排序在前的先進行處理。
那么會不會出現在ImportBeanDefinitionRegistrar#registerBeanDefinitions中使用beanFactory.getBean方法獲取bean而報錯的場景呢?答案是會,假如定義兩個@Configuration標注的類,a和b,a先于b處理,a通過@Import導入TestAutowiredRegistar,b中定義TestRegistarDependOn的bean實例化方法,代碼如下。
配置類:
@Configuration @Order(1) @Import(TestAutowiredRegistar.class) public class FirstConfig { //1.先處理TestAutowiredRegistar的ImportBeanDefinitionRegistrar#registerBeanDefinitions
@Configuration @Order(2) public class SecondConfig { @Bean public TestRegistarDependOn testRegistarDependOn(){ return new TestRegistarDependOn(); } }
TestRegistarDependOn去掉@Component注解,避免被掃描到提前注冊BeanDefinition
引導程序,去掉提前Import TestAutowiredRegistar.class
@SpringBootApplication(scanBasePackages = "garine.learn.test.auwired") public class BootstrapTestApplication3 { public static void main(String[] args) { ApplicationContext applicationContext = SpringApplication.run(BootstrapTestApplication3.class, args); } }
執行啟動程序,輸出報錯信息
A component required a bean of type ‘garine.learn.test.auwired.TestRegistarDependOn' that could not be found.
所以實際上在getBean時,如果bean的BeanDefinition并沒有注冊到Beanfactory,那么久會報出上述錯誤。
把兩個配置類的@Order順序換一下,就能處理成功,執行輸出。--------------------------然并卵。。。一樣報錯,
what?源碼里面明明有排序的,在
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
對所有的配置類都有
// Sort by previously determined @Order value, if applicable configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); });
經過調試發現,應該是在spring.factories中定義的Configuration類才會在這里做處理,可以稱之為最高優先級配置,對于這些配置@Order才會起作用。
那么我們自定義的@Configuration標注的類在哪里處理?經過調試,定位在@ComponentScan注解處理處,有如下代碼。
// Process any @ComponentScan annotations Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } //判斷掃描到的是不是配置類,是的話就進行配置處理 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } }
也就是說,我們一般自定義的配置順序@Order是不起作用的,全靠掃描文件得到的先后順序,所以,文件名稱是關鍵。。
這里把FirstConfig改成TFirstConfig試試,輸出
TestAutowiredRegistar-----null
TestAutowiredRegistar-----garine.learn.test.auwired.TestRegistarDependOn@189b5fb1
所以猜想通過??偨Y就是@Order對spring.factories中定義的配置類起作用,我們自定義的配置類處理順序需要文件名稱來控制。
在Configuration中使用@Autowired注解
通過調試都可以知道@Bean注冊實例的方式,實際代理調用@Bean修飾的方法是在
// 2. 根據BeanDefinition處理Bean實例化過程 finishBeanFactoryInitialization(beanFactory);
的過程中的,所以假如在@Bean修飾的方法中使用到@Autowired注解依賴的bean是怎么樣的場景?
spring 實例化Bean過程
先了解一下實例化bean的過程是怎么樣的。在finishBeanFactoryInitialization中,遍歷所有的BeanDefinition信息來實例化bean。遍歷的順序調試幾次后發現是按照注冊BeanDefinition信息的先后順序。所以可以有幾個簡單規則可以判斷哪個bean會先實例化。
- 同是@ComponentScan掃描注冊的bean,按照class文件名順序,排在前面的先注冊,所以先實例化,例如 ATest類實例化先于BTest類.
- @Conguration 配置類實例化先于其內部定義的@Bean方法執行實例化,例如Config類實例化先于其內部任意@Bean 方法實例化bean。
那么考慮,假如在@Conguration 修飾的類的@Bean方法里面使用@Autowired引入依賴,而這個依賴實例化順序要比@Conguration 修飾的類要遲,會怎么樣?
定義下面三個類,在同一個包里面順序也是如下所示:
-
ConfigInitBean
-
TestDependOnConfig
-
ZTestConfigurationDependOn
各自代碼如下:
public class ConfigInitBean { }
@Configuration public class TestDependOnConfig { @Autowired ZTestConfigurationDependOn zTestConfigurationDependOn;//觀察這個依賴什么時候進行初始化,斷點getBean調試 @Bean public ConfigInitBean configInitBean(){ zTestConfigurationDependOn.saySome(); return new ConfigInitBean(); } }
@Component public class ZTestConfigurationDependOn { public void saySome(){ System.out.println("say some"); } }
在DefaultListableBeanFactory#preInstantiateSingletons方法中斷點查看beanNames的順序
根據spring 對@Configuration標注的類的處理過程,能夠對應的上,先掃描到TestDependOnConfig所以先注冊,ZTestConfigurationDependOn后掃描所以比TestDependOnConfig實例化要晚。ConfigInitBean是由@Bean定義的,在對配置類的處理中,都是先處理完@ComponentScan的BeanDefinition注冊,再處理@Bean、@Import導入的配置、@ImportResource導入的xml等等BeanDefinition注冊。
總結來說就是bean實例化的順序符合猜想,實際上還有一點就是每個bean實例化時,都會對其@Autowired注解的依賴進行注入,如果當時依賴沒有實例化,就根據依賴的BeanDefinition進行getBean過程所以一般情況下,我們平常使用業務代碼模型都不會出現注入為null問題。
當然,如果依賴的Beandefinition不存在,那么就會報錯:
Consider defining a bean of type ‘XXXx' in your configuration.
在這里例子中,TestDependOnConfig依賴ZTestConfigurationDependOn,但是比ZTestConfigurationDependOn實例化要早,所以會調用getBean.ZTestConfigurationDependOn,提前實例化ZTestConfigurationDependOn來注入依賴。
具體源碼在AbstractAutowireCapableBeanFactory#populateBean方法中,填充bean。
for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; //填充屬性 pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvs == null) { return; } } }
填充具體使用的實現方法是AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues來進行填充屬性,最終會調用依賴bean的getBean,從而實例化依賴的bean或者直接獲取依賴bean。
所以就算是配置類也好,普通的組件也好,都會在實例化時注入@Autowired依賴的屬性實例,如果是該實例沒有定義BeanDefinition,那么就會無法注入。
@Bean內部使用配置類@Autowired注解引入依賴
了解完上面的過程,可以知道,@Bean方法是在finishBeanFactoryInitialization過程實例化對應的bean時才會被代理調用,并且順序比對應配置類要后,這時對應配置類早已經實例化完畢,依賴屬性也已經注入,可以放心在@Bean方法內部使用。
還有一種情況是,假如@Bean方法被提前調用,例如@Bean的實例被另一個比@Bean所在配置類還要早實例化的組件中引入,那么此時@Bean所在配置類還沒實例化,這樣調用會出錯嗎?答案是不會,因為歸根到底,@Bean方法的調用都是代理方式,程序還是需要先實例化一個@Bean所在配置類的實例,才能進行@Bean方法的調用,從而實例化一個@Bean方法的bean。
InitializingBean#afterPropertiesSet內部使用依賴
了解到上面的知識,推測一下,在InitializingBean#afterPropertiesSet里面使用@Autowired依賴進行邏輯處理是否可以?看如下InitializingBean#afterPropertiesSet的調用時機。
try { //填充依賴屬性 populateBean(beanName, mbd, instanceWrapper); //最終調用到InitializingBean#afterPropertiesSet方法 exposedObject = initializeBean(beanName, exposedObject, mbd); }
所以,InitializingBean#afterPropertiesSet是在填充玩依賴屬性之后調用的,因此可以使用依賴的bean進行一些邏輯操作的。
總結
1、所以總結來說就是,我們的代碼邏輯在
// 根據BeanDefinition處理Bean實例化過程 finishBeanFactoryInitialization(beanFactory);
過程之前都沒有使用的@Autowired依賴bean的話,那是沒問題的,因為@Autowired注解處理都是在finishBeanFactoryInitialization()也就是bean實例化時才會進行處理。如果使用了,那就是空指針;
2、在@Bean方法內部使用@Autowired注解的依賴,只要設計好程序也是可以的的,只要依賴的BeanDefinition已經注冊過,配置類實例化時就能主動發起依賴的實例化過程,然后注入依賴,不會出現空指針。
而@Bean方法是在finishBeanFactoryInitialization過程實例化對應的bean時才會被代理調用,并且順序比對應配置類要后,這時對應配置類早已經實例化完畢,依賴屬性也已經注入,可以放心在@Bean方法內部使用。
所以這里@Bean方法實例化bean時如果使用到@Autowired依賴的bean時,就對配置類的實例有很強的依賴性,這種依賴順序spring都幫我們保證先實例化配置類,再調用@Bean方法。
3、InitializingBean#afterPropertiesSet內部使用也是沒問題,原理如上。所以只要理解在使用到@Autowired的依賴時,到底在哪個時機,就能分析清楚是不是適合使用。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/qq_20597727/article/details/82833843