天天看點

深入了解Spring的依賴查找和依賴注入【建議收藏】依賴注入的模式和類型Spring Bean的來源Spring 依賴處理的過程如何解決單例Bean中需要注入Prototype作用域Bean的問題靜态@Bean方法和執行個體@Bean方法的差別怎麼實作不注入Spring容器,卻也完成依賴注入?

如果你覺得内容對你有幫助的話,不如給個贊,鼓勵一下更新😂。
本文内容來自《小馬哥講Spring核心程式設計思想》,基本涵蓋了工作中用到的注入相關的概念,所涵蓋的知識點較多。

依賴注入的模式和類型

依賴注入的模式

手動模式 - 配置或者程式設計的方式,提前安排注入規則

  • XML 資源配置元資訊
  • Java 注解配置元資訊,比如@Autowired、@Resource
  • API 配置元資訊

自動模式 - 實作方提供依賴自動關聯的方式,按照內建的注入規則

  • Autowiring(自動綁定)

依賴注入的類型

依賴注入類型 配置中繼資料舉例
Setter 方法
構造器
字段 @Autowired User user;
方法 @Autowired public void user(User user) { … }
接口回調 class MyBean implements BeanFactoryAware { … }

構造器和Setter注入的利與弊

使用構造器注入的好處:

  1. 保證依賴不可變(final關鍵字)
  2. 保證依賴不為空(省去了我們對其檢查)
  3. 保證傳回用戶端(調用)的代碼的時候是完全初始化的狀态
  4. 避免了循環依賴
  5. 避免了和容器的高度耦合,提升了代碼的可複用性

總結: 構造器注入适用于強制對象注入;Setter 注入适合于可選對象注入;并且構造器注入在構造過程中可以保證線程的安全

自動綁定(Autowiring)的模式

模式 說明
no 預設值,未激活 Autowiring,需要手動指定依賴注入對象。
byName 根據被注入屬性的名稱作為 Bean 名稱進行依賴查找,并将對象設定到該屬性。
byType 根據被注入屬性的類型作為依賴類型進行查找,并将對象設定到該屬性。
constructor 特殊 byType 類型,用于構造器參數。

延遲依賴查找、延遲依賴注入

Bean 延遲依賴查找接口

  • org.springframework.beans.factory.ObjectFactory
  • org.springframework.beans.factory.ObjectProvider

Bean 延遲依賴注入也是這兩個:

  • 使用 API ObjectFactory 延遲注入
  • 使用 API ObjectProvider 延遲注入(推薦)

限定注入@Qualifier

  • 通過Bean名稱限定
  • 通過分組限定

實作@Autowired和@Resource等注解的後置處理器

  • AutowiredAnnotationBeanPostProcessor
    • 處理 @Autowired 以及 @Value 注解
    • 在postProcessMergedBeanDefinition方法中查找自動注入的元資訊來封裝注入元素資訊
    • 在postProcessProperties方法中實作屬性和方法注入
  • CommonAnnotationBeanostProcessor(生命周期注解也是在這裡面實作的)
    • (條件激活)處理 JSR-250 注解,如 @PostConstruct 等

@Autowired注入過程

所有方法都在AutowiredAnnotationBeanPostProcessor#類裡

  • 調用postProcessProperties()方法(spring 5.1之後才是這個方法名,5.1之前是postProcessPropertyValues)
    • postProcessProperties方法會比bean的setXX()方法先調用
    • findAutowiringMetadata()方法會找出一個bean加了@Autowired注解的字段(包括父類的),并且該方法做了緩存
    • xml配置的bean與bean之間是可以有繼承關系的,有另一個周期(不是autowired的流程)是把配置super bean的屬性合并到目前bean,之後會調用後置方法postProcessMergedBeanDefinition,該方法也會調用一次findAutowiringMetadata
    • 經測試,postProcessMergedBeanDefinition會比postProcessProperties先執行,是以調用postProcessProperties時都是直接拿緩存
  • 調用inject方法, 底層調用的是AutowiredFieldElement和AutowiredMethodElement的inject方法。(獲得對應的bean,然後通過反射注入到類的字段上)
  • inject方法會調用DefaultListableBeanFactory#resolveDependency方法,這方法會根據@Autowired字段資訊來比對出符合條件的bean

Spring Bean的來源

依賴查找的來源

來源 配置中繼資料
Spring BeanDefinition
@Bean public User user(){…}
BeanDefinitionBuilder
單例對象 API實作

依賴注入的來源

來源 配置中繼資料
Spring BeanDefinition
@Bean public User user(){…}
BeanDefinitionBuilder
單例對象 API實作
非Spring容器管理對象
外部化配置作為依賴來源

Spring容器管理和遊離對象

來源 配置中繼資料 生命周期管理 配置元資訊 使用場景
Spring BeanDefinition 依賴查找、依賴注入
單例對象 依賴查找、依賴注入
ResolvableDependency 依賴注入

Spring BeanDefinition 作為依賴來源

  • 中繼資料:BeanDefinition
  • 注冊:
    • 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
    • 非命名方式: BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,Be anDefinitionRegistry)
    • 配置類方式:AnnotatedBeanDefinitionReader#register(Class…)
  • 類型:延遲和非延遲
  • 順序:Bean 生命周期順序按照注冊順序
  • 舉例:
    • 實作

      ImportBeanDefinitionRegistrar

    • 實作

      BeanDefinitionRegistryPostProcessor

單例對象作為依賴來源

  • 要素
    • 來源:外部普通Java 對象(不一定是POJO)
    • 注冊:SingletonBeanRegistry#registerSingleton
  • 限制
    • 無生命周期管理
    • 無法實作延遲初始化Bean

非Spring容器管理對象作為依賴來源

  • 要素
    • 注冊:ConfigurableListableBeanFactory#registerResolvableDependency
  • 限制
    • 無生命周期管理
    • 無法實作延遲初始化Bean
    • 無法通過依賴查找

registerResolvableDependency和registerSingleton的差別:

前者是注冊可以解析的依賴關系,當注入的類型為dependencyType的時候,注入autowiredValue,并将注入類型與注入值的關系存儲在map中。常用于解決在Spring架構内部自動裝配的時候如果一個接口有多個實作類,并且都已經放到IOC中去了,那麼自動裝配的時候就會出異常,因為spring不知道把哪個實作類注入進去的問題。

外部化配置作為依賴來源

  • 要素
    • 類型:非正常Spring對象依賴來源
  • 限制
    • 無生命周期管理
    • 無法實作延遲初始化Bean
    • 無法通過依賴查找

Spring 依賴處理的過程

Spring依賴處理是依賴注入的一個環節,就是說在注入過程中我們把這個對象的依賴來進行解析,然後利用反射進行指派注入。

  • 入口 - DefaultListableBeanFactory#resolveDependency
  • 依賴描述符 - DependencyDescriptor
  • 自定綁定候選對象處理器 - AutowireCandidateResolver

bean的屬性填充populateBean方法最終會走到DefaultListableBeanFactory#resolveDependency方法,最後還是會走到AbstractBeanFactory#getBean方法中去擷取Bean

如何解決單例Bean中需要注入Prototype作用域Bean的問題

在大多數應用場景中,容器中大部分bean都是singleton。當一個單例bean需要與另一個單例bean協作或一個非單例bean需要與另一個非單例bean協作時,通常是将一個bean定義為另一個bean的屬性來處理依賴關系。當bean的生命周期不同時,就會出現問題。可能是在A的每個方法調用上,假設單例bean A需要使用非單例(原型)bean B。容器隻會建立單例bean A一次,是以隻有一次機會來設定屬性。容器無法在每次進行A方法調用時,都為bean A提供一個全新的bean B執行個體。(注:也就是多例可以調用單例,但是單例無法調用多例)

解決方案是放棄一些控制翻轉。通過實作 

ApplicationContextAware

 接口,進而使bean A能夠拿到容器的上下文 ,并在每次bean A 需要bean B時,通過調用容器上下文的

getBean("B")

方法來請求得到(通常是新建立的)bean B執行個體。

使用ApplicationContextAware回調接口解決

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
           

但是以上方法并不可取,因為業務代碼會使用Spring内部的上下文,也就是會和Spring Framework耦合到一起

方法注入

查找方法注入是容器覆寫受容器管理的bean上的方法并傳回容器中另一個命名的bean的查找結果的能力。查找通常涉及一個原型bean。Spring架構通過使用CGLIB庫中的位元組碼生成來動态生成覆寫該方法的子類。

方法注入條件:

  • 為了使這動态子類起作用,Spring容器的子類class不能用

    final

    修飾,且重寫的方法也不能用

    final

    修飾。
  • 對具有抽象方法的類進行單元測試,需要你自己建立這個類的子類并對

    abstract

    方法進行實作。
  • 元件掃描也需要具體的方法,這需要具體的類去支援。
  • 進一步的關鍵限制是查找方法對工廠方法不起作用,尤其不适用于配置類中的

    @Bean

    方法,因為在這種情況下,容器不負責建立執行個體,是以不能動态地建立運作時生成的子類。

在包含注入方法的用戶端類(本例中 

CommandManager

)中,要求注入的方法具有以下簽名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);
           

如果方法是 

abstract

 的,則動态生成的子類将實作該方法。否則,動态生成的子類将覆寫原始類中定義的具體方法。

注解版本實作:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}
           

XML實作:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>
           

靜态@Bean方法和執行個體@Bean方法的差別

設定方法為static,是因為bean定義的一個周期性問題:

  • 如果是非static,那麼這個bean的建構是依賴于聲明類的這個bean來處理的。
  • 如果是static,那麼它就脫離了這個實作,變成了一個獨立的bean,是以如果你需要bean初始化或提前初始化,那麼可以選擇性的标注成static。

具體細節在ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod方法中,如下:

if (metadata.isStatic()) {
    // static @Bean method
    beanDef.setBeanClassName(configClass.getMetadata().getClassName());
    beanDef.setFactoryMethodName(methodName);
}
else {
    // instance @Bean method
    beanDef.setFactoryBeanName(configClass.getBeanName());
    beanDef.setUniqueFactoryMethodName(methodName);
}
           

@Bean方法處理的時機

詳細請看ConfigurationClassPostProcessor類 => ConfigurationClassParser​

怎麼實作不注入Spring容器,卻也完成依賴注入?

本着"減輕"Spring容器"負擔"的目的,"手動"精細化控制Spring内的Bean元件。像有的這種解析器其實是完全沒必要放進容器内的,需要什麼元件讓容器幫你完成注入即可,自己就沒必要放進去

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private ApplicationContext applicationContext;
    
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        CurrUserArgumentResolver resolver = new CurrUserArgumentResolver();
        // 利用工廠給容器外的對象注入所需元件
        applicationContext.getAutowireCapableBeanFactory().autowireBean(resolver);
        argumentResolvers.add(resolver);
    }
}

           

本姿勢的技巧是利用了

AutowireCapableBeanFactory

巧妙完成了給外部對象賦能,進而即使自己并不是容器内的Bean,也能自由注入、使用容器内

Bean

的能力(同樣可以随意使用

@Autowired

注解了~),這種方式是侵入性最弱的。