欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳談spring boot中幾種常見的依賴注入問題

 更新時間:2021年09月28日 10:49:57   作者:一擼向北  
這篇文章主要介紹了spring boot中幾種常見的依賴注入問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

最近有空總結一下之前在使用spring boot時遇到過的幾種依賴注入時的坑,如果不了解spring內(nèi)部的處理過程,使用起來總是感覺有種迷糊。

在分析場景前,需要大概了解一下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. 根據(jù)BeanDefinition處理Bean實例化過程
			finishBeanFactoryInitialization(beanFactory);
			// Last step: publish corresponding event.
			finishRefresh();
		}

@Autowired依賴注入問題–邏輯使用先于@Autowired注解處理

之前不熟悉spring bean的實例化過程可能會遇到的坑就是使用@Autowired依賴注入的對象是null沒有注入到相應的對象里面,或者準確的來說是在我程序的某一塊邏輯代碼執(zhí)行時使用到@Autowired依賴的bean,但是bean確實null。

這種場景一般就是在當時,@Autowired注解并沒有被處理,所以依賴的bean為null。

如果了解spring boot自動化裝配可以直達我們通過實現(xiàn)ImportBeanDefinitionRegistrar接口自定義注冊BeanDefinition信息,實現(xiàn)邏輯是ImportBeanDefinitionRegistrar#registerBeanDefinitions的具體實現(xiàn)中,這個方法的執(zhí)行過程屬于

​// 1. 包含了BeanDefinition注冊過程
invokeBeanFactoryPostProcessors(beanFactory);

之前曾經(jīng)遇到過在自定義的ImportBeanDefinitionRegistrar實現(xiàn)類中,使用@Autowired依賴某個bean,但是在使用時無法得到具體的實現(xiàn)對象,因為@Autowired注解的處理過程是在

​//2. 根據(jù)BeanDefinition處理Bean實例化過程
​finihBeanFactoryInitialization(beanFactory);

當程序執(zhí)行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 {
}

執(zhí)行輸出:TestAutowiredRegistar-----null

**這種場景一般就是在當時,@Autowired注解并沒有被處理,所以依賴的bean為null。**如果遇到依賴注入為空時,如果確定已經(jīng)定義了對應的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));
}

再次執(zhí)行輸出:

TestAutowiredRegistar-----null

TestAutowiredRegistar-----garine.learn.test.auwired.TestRegistarDependOn@7808fb9

BeanFactory.getBean問題–getBean調(diào)用先于BeanDefinition信息注冊

這里就是beanFactory.getBean方法如果獲取不到bean就會調(diào)用bean的初始化過程。但是需要注意bean對應的BeanDefinition信息必須已經(jīng)注冊完成。所以這種getBean的方式不是絕對安全。

一般而言ConfigurationClassParser#processConfigurationClass為入口,可以看到整個對Configclass的處理過程。對于@Configuration標注的類都是有排序的,排序在前的先進行處理。

那么會不會出現(xiàn)在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);
    }
}

執(zhí)行啟動程序,輸出報錯信息

A component required a bean of type ‘garine.learn.test.auwired.TestRegistarDependOn' that could not be found.

所以實際上在getBean時,如果bean的BeanDefinition并沒有注冊到Beanfactory,那么久會報出上述錯誤。

把兩個配置類的@Order順序換一下,就能處理成功,執(zhí)行輸出。--------------------------然并卵。。。一樣報錯,

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);
  });

經(jīng)過調(diào)試發(fā)現(xiàn),應該是在spring.factories中定義的Configuration類才會在這里做處理,可以稱之為最高優(yōu)先級配置,對于這些配置@Order才會起作用。

那么我們自定義的@Configuration標注的類在哪里處理?經(jīng)過調(diào)試,定位在@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注解

通過調(diào)試都可以知道@Bean注冊實例的方式,實際代理調(diào)用@Bean修飾的方法是在

// 2. 根據(jù)BeanDefinition處理Bean實例化過程
finishBeanFactoryInitialization(beanFactory);

的過程中的,所以假如在@Bean修飾的方法中使用到@Autowired注解依賴的bean是怎么樣的場景?

spring 實例化Bean過程

先了解一下實例化bean的過程是怎么樣的。在finishBeanFactoryInitialization中,遍歷所有的BeanDefinition信息來實例化bean。遍歷的順序調(diào)試幾次后發(fā)現(xiàn)是按照注冊BeanDefinition信息的先后順序。所以可以有幾個簡單規(guī)則可以判斷哪個bean會先實例化。

  • 同是@ComponentScan掃描注冊的bean,按照class文件名順序,排在前面的先注冊,所以先實例化,例如 ATest類實例化先于BTest類.
  • @Conguration 配置類實例化先于其內(nèi)部定義的@Bean方法執(zhí)行實例化,例如Config類實例化先于其內(nèi)部任意@Bean 方法實例化bean。

那么考慮,假如在@Conguration 修飾的類的@Bean方法里面使用@Autowired引入依賴,而這個依賴實例化順序要比@Conguration 修飾的類要遲,會怎么樣?

定義下面三個類,在同一個包里面順序也是如下所示:

  • ConfigInitBean
  • TestDependOnConfig
  • ZTestConfigurationDependOn

各自代碼如下:

public class ConfigInitBean {
}
@Configuration
public class TestDependOnConfig {
    @Autowired
    ZTestConfigurationDependOn zTestConfigurationDependOn;//觀察這個依賴什么時候進行初始化,斷點getBean調(diào)試
    @Bean
    public ConfigInitBean configInitBean(){
        zTestConfigurationDependOn.saySome();
        return new ConfigInitBean();
    }
}
@Component
public class ZTestConfigurationDependOn {
    public void saySome(){
        System.out.println("say some");
    }
}

在DefaultListableBeanFactory#preInstantiateSingletons方法中斷點查看beanNames的順序

在這里插入圖片描述

根據(jù)spring 對@Configuration標注的類的處理過程,能夠對應的上,先掃描到TestDependOnConfig所以先注冊,ZTestConfigurationDependOn后掃描所以比TestDependOnConfig實例化要晚。ConfigInitBean是由@Bean定義的,在對配置類的處理中,都是先處理完@ComponentScan的BeanDefinition注冊,再處理@Bean、@Import導入的配置、@ImportResource導入的xml等等BeanDefinition注冊。

具體可以看有關自動裝配的文章

總結來說就是bean實例化的順序符合猜想,實際上還有一點就是每個bean實例化時,都會對其@Autowired注解的依賴進行注入,如果當時依賴沒有實例化,就根據(jù)依賴的BeanDefinition進行getBean過程所以一般情況下,我們平常使用業(yè)務代碼模型都不會出現(xiàn)注入為null問題。

當然,如果依賴的Beandefinition不存在,那么就會報錯:

Consider defining a bean of type ‘XXXx' in your configuration.

在這里例子中,TestDependOnConfig依賴ZTestConfigurationDependOn,但是比ZTestConfigurationDependOn實例化要早,所以會調(diào)用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;
      }
   }
}

填充具體使用的實現(xiàn)方法是AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues來進行填充屬性,最終會調(diào)用依賴bean的getBean,從而實例化依賴的bean或者直接獲取依賴bean。

所以就算是配置類也好,普通的組件也好,都會在實例化時注入@Autowired依賴的屬性實例,如果是該實例沒有定義BeanDefinition,那么就會無法注入。

@Bean內(nèi)部使用配置類@Autowired注解引入依賴

了解完上面的過程,可以知道,@Bean方法是在finishBeanFactoryInitialization過程實例化對應的bean時才會被代理調(diào)用,并且順序比對應配置類要后,這時對應配置類早已經(jīng)實例化完畢,依賴屬性也已經(jīng)注入,可以放心在@Bean方法內(nèi)部使用。

還有一種情況是,假如@Bean方法被提前調(diào)用,例如@Bean的實例被另一個比@Bean所在配置類還要早實例化的組件中引入,那么此時@Bean所在配置類還沒實例化,這樣調(diào)用會出錯嗎?答案是不會,因為歸根到底,@Bean方法的調(diào)用都是代理方式,程序還是需要先實例化一個@Bean所在配置類的實例,才能進行@Bean方法的調(diào)用,從而實例化一個@Bean方法的bean。

InitializingBean#afterPropertiesSet內(nèi)部使用依賴

了解到上面的知識,推測一下,在InitializingBean#afterPropertiesSet里面使用@Autowired依賴進行邏輯處理是否可以?看如下InitializingBean#afterPropertiesSet的調(diào)用時機。

	try {
	//填充依賴屬性
		populateBean(beanName, mbd, instanceWrapper);
		//最終調(diào)用到InitializingBean#afterPropertiesSet方法
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}

所以,InitializingBean#afterPropertiesSet是在填充玩依賴屬性之后調(diào)用的,因此可以使用依賴的bean進行一些邏輯操作的。

總結

1、所以總結來說就是,我們的代碼邏輯在

// 根據(jù)BeanDefinition處理Bean實例化過程
finishBeanFactoryInitialization(beanFactory);

過程之前都沒有使用的@Autowired依賴bean的話,那是沒問題的,因為@Autowired注解處理都是在finishBeanFactoryInitialization()也就是bean實例化時才會進行處理。如果使用了,那就是空指針;

2、在@Bean方法內(nèi)部使用@Autowired注解的依賴,只要設計好程序也是可以的的,只要依賴的BeanDefinition已經(jīng)注冊過,配置類實例化時就能主動發(fā)起依賴的實例化過程,然后注入依賴,不會出現(xiàn)空指針。

而@Bean方法是在finishBeanFactoryInitialization過程實例化對應的bean時才會被代理調(diào)用,并且順序比對應配置類要后,這時對應配置類早已經(jīng)實例化完畢,依賴屬性也已經(jīng)注入,可以放心在@Bean方法內(nèi)部使用。

所以這里@Bean方法實例化bean時如果使用到@Autowired依賴的bean時,就對配置類的實例有很強的依賴性,這種依賴順序spring都幫我們保證先實例化配置類,再調(diào)用@Bean方法。

3、InitializingBean#afterPropertiesSet內(nèi)部使用也是沒問題,原理如上。所以只要理解在使用到@Autowired的依賴時,到底在哪個時機,就能分析清楚是不是適合使用。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

最新評論