亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

在分析場(chǎng)景前,需要大概了解一下spring對(duì)于bean的實(shí)例化過程是需要先注冊(cè)BeanDefinition信息然后才進(jìn)行實(shí)例化,在org.springframework.context.support.AbstractApplicationContext#refresh中定義的基本的流程。部分代碼

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);
			// 1. 包含了BeanDefinition注冊(cè)過程
			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實(shí)例化過程
			finishBeanFactoryInitialization(beanFactory);
			// Last step: publish corresponding event.
			finishRefresh();
		}

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

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

這種場(chǎng)景一般就是在當(dāng)時(shí),@Autowired注解并沒有被處理,所以依賴的bean為null。

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

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

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

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

當(dāng)程序執(zhí)行ImportBeanDefinitionRegistrar#registerBeanDefinitions時(shí),依賴的bean為null,報(bào)空指針。

測(cè)試用例

引導(dǎo)程序代碼

@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

**這種場(chǎng)景一般就是在當(dāng)時(shí),@Autowired注解并沒有被處理,所以依賴的bean為null。**如果遇到依賴注入為空時(shí),如果確定已經(jīng)定義了對(duì)應(yīng)的bean,那么不妨看看代碼使用依賴bean時(shí),到底@Autowired注解有沒有被處理。

這種場(chǎng)景的解決辦法就是使用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信息注冊(cè)

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

一般而言ConfigurationClassParser#processConfigurationClass為入口,可以看到整個(gè)對(duì)Configclass的處理過程。對(duì)于@Configuration標(biāo)注的類都是有排序的,排序在前的先進(jìn)行處理。

那么會(huì)不會(huì)出現(xiàn)在ImportBeanDefinitionRegistrar#registerBeanDefinitions中使用beanFactory.getBean方法獲取bean而報(bào)錯(cuò)的場(chǎng)景呢?答案是會(huì),假如定義兩個(gè)@Configuration標(biāo)注的類,a和b,a先于b處理,a通過@Import導(dǎo)入TestAutowiredRegistar,b中定義TestRegistarDependOn的bean實(shí)例化方法,代碼如下。

配置類:

@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注解,避免被掃描到提前注冊(cè)BeanDefinition

引導(dǎo)程序,去掉提前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í)行啟動(dòng)程序,輸出報(bào)錯(cuò)信息

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

所以實(shí)際上在getBean時(shí),如果bean的BeanDefinition并沒有注冊(cè)到Beanfactory,那么久會(huì)報(bào)出上述錯(cuò)誤。

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

what?源碼里面明明有排序的,在

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

對(duì)所有的配置類都有

// 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),應(yīng)該是在spring.factories中定義的Configuration類才會(huì)在這里做處理,可以稱之為最高優(yōu)先級(jí)配置,對(duì)于這些配置@Order才會(huì)起作用。

那么我們自定義的@Configuration標(biāo)注的類在哪里處理?經(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();
         }
         //判斷掃描到的是不是配置類,是的話就進(jìn)行配置處理
         if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
            parse(bdCand.getBeanClassName(), holder.getBeanName());
         }
      }
   }
}

也就是說,我們一般自定義的配置順序@Order是不起作用的,全靠掃描文件得到的先后順序,所以,文件名稱是關(guān)鍵。。

這里把FirstConfig改成TFirstConfig試試,輸出

TestAutowiredRegistar-----null

TestAutowiredRegistar-----garine.learn.test.auwired.TestRegistarDependOn@189b5fb1

所以猜想通過。總結(jié)就是@Order對(duì)spring.factories中定義的配置類起作用,我們自定義的配置類處理順序需要文件名稱來控制。

在Configuration中使用@Autowired注解

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

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

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

spring 實(shí)例化Bean過程

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

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

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

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

  • ConfigInitBean
  • TestDependOnConfig
  • ZTestConfigurationDependOn

各自代碼如下:

public class ConfigInitBean {
}
@Configuration
public class TestDependOnConfig {
    @Autowired
    ZTestConfigurationDependOn zTestConfigurationDependOn;//觀察這個(gè)依賴什么時(shí)候進(jìn)行初始化,斷點(diǎn)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方法中斷點(diǎn)查看beanNames的順序

在這里插入圖片描述

根據(jù)spring 對(duì)@Configuration標(biāo)注的類的處理過程,能夠?qū)?yīng)的上,先掃描到TestDependOnConfig所以先注冊(cè),ZTestConfigurationDependOn后掃描所以比TestDependOnConfig實(shí)例化要晚。ConfigInitBean是由@Bean定義的,在對(duì)配置類的處理中,都是先處理完@ComponentScan的BeanDefinition注冊(cè),再處理@Bean、@Import導(dǎo)入的配置、@ImportResource導(dǎo)入的xml等等BeanDefinition注冊(cè)。

具體可以看有關(guān)自動(dòng)裝配的文章

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

當(dāng)然,如果依賴的Beandefinition不存在,那么就會(huì)報(bào)錯(cuò):

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

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

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

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

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

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

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

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

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

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

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

總結(jié)

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

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

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

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

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

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

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

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

相關(guān)文章

最新評(píng)論