天天看點

十天8000字,我總結了這一篇Spring容器管理知識大全

作者:軟體程式設計指南
十天8000字,我總結了這一篇Spring容器管理知識大全
天下代碼一大抄, 抄來抄去有提高, 看你會抄不會抄!
十天8000字,我總結了這一篇Spring容器管理知識大全

一、前言

十天8000字,我總結了這一篇Spring容器管理知識大全

Spring 是 java 開發者,永遠繞不開的結。Spring 是非常值得開發者來學習的, 以目前 Spring 在 java 領域的統治性地位, 可以說學 java 就是在學 Spring。但是作為新入門的開發人員,甚至說是有一定工作經驗的同學,面對如此龐大的架構,都不一定是充分掌握了所有的知識點。因為大多數人的學習,都不是系統的學習,都是片面的。以經驗為主。本系列專題的主要目的就是,一起系統的來學習一下Spring這個架構, 以一個六年經驗的老鳥的視角裡,來重學Spring。通過直接閱讀 Spring的官方文檔來擷取一手知識。

因為内容較多,建議收藏學習。

二、BeanFactory 工廠

十天8000字,我總結了這一篇Spring容器管理知識大全

2.1 什麼是Bean ?

平時我們來建立對象, 一般都是 new。如果這個對象裡有一個屬性, 那麼就需要我來進行set,指派。但是如果要有10個屬性呢? 你也要自己來指派嗎? 那不累死個人嘛。Spring的解決方案就是, 這麼重的活, 開發者不用關心了,都交給我來處理吧。那麼Spring是如何來處理的呢? 對,就是BeanFactory,Spring通過 BeanFactory的方式幫實作對象的執行個體化。那麼所有被Spring管理的對象,我們就可以了解成Bean對象。

凡是有屬性和方法的對象都是Bean對象,凡是被Spring管理的Bean對象就是Spring Bean對象。

2.2 如何使用Bean工廠

  • 方式一直接使用代碼自動注入
@Component
public class SpringIocTest{

    @Autowired
    private BeanFactory beanFactory;
}    
           
  • 方式二使用BeanFactoryAware注入
@Component
public class SpringIocTest implements BeanFactoryAware {

    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }
}    
           

2.3 BeanFactory的體系

在 Spring 中 BeanFactory 是一個非常重要的元件, 要想搞清楚 Spring, 一定要先搞清楚 BeanFactory 的體系,這裡我們詳細來解釋下 BeanFactory的體系。

十天8000字,我總結了這一篇Spring容器管理知識大全

看這張圖,密密麻麻的都是,但是我們不要擔心,實際我們不用關心這麼多。大部分人都是因為看到了這裡,給勸退了, 下面給大家精簡一下。希望對你有所幫助。

十天8000字,我總結了這一篇Spring容器管理知識大全

我們隻關心上面這張圖就好了,但是看類還是比較多,為什麼呢? 因為Spring定義BeanFactory接口比較細,每個接口的次元都很細次元。但是我們能看到最底層的實作,是實作了所有接口的功能。下面我們以此來解釋每個接口的功能。來窺探一下Spring中BeanFactory的體系。非常的全,建議大家可以收藏一下,沒必要死記硬背。如果不了解的話,背下來也沒有什麼的用。

下面分享,希望對大家有點用。

2.3.1 BeanFactory

最頂層的接口,提供了根據Bean名稱擷取Bean的最基礎的能力。詳細可以看下面的注釋說明。接口沒有任何實作,隻是做定義。

public interface BeanFactory {

 // 如果要擷取FactoryBean,那麼要的Bean的名稱前加 &
 String FACTORY_BEAN_PREFIX = "&";

 // 根據名稱擷取執行個體,如果沒有就抛異常,結果是Object類型
 Object getBean(String name) throws BeansException;

 // 跟前者一樣,不同是結果是泛型類型,會自動幫我們轉換類型
 <T> T getBean(String name, Class<T> requiredType) throws BeansException;

 // 允許指定顯式構造函數參數,很少會用
 Object getBean(String name, Object... args) throws BeansException;

 // 根據類型擷取Bean執行個體,如果找到了多個類型,則會報錯
 <T> T getBean(Class<T> requiredType) throws BeansException;

 // 根據類型擷取執行個體,并顯式構造函數參數
 <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

 // 根據類型擷取Bean的生成對象,這裡并不是直接擷取了Bean的執行個體
 <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

 // 跟前者大同小異
 <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

 // 判斷是否儲存這個名字的執行個體
 boolean containsBean(String name);

 // 判斷是否單例
 boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

 // 判斷是否是原型模式
 boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

 // bean名稱和類型是否比對
 boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

 // bean名稱和類型是否比對
 boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

 // 擷取名稱的類型
 @Nullable
 Class<?> getType(String name) throws NoSuchBeanDefinitionException;

 // 根據名稱擷取類型,FactoryBean比較特殊,allowFactoryBeanIn   // it是說,是否也要算FactoryBean,一般情況用true
 @Nullable
 Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;

 // bean聲明的别名,如果沒有則為空數組
 String[] getAliases(String name);

}

           

2.3.2 HierarchicalBeanFactory

Hierarchical翻譯: 分層

HierarchicalBeanFactory的意思是具有層次關系,這個BeanFactory可以建立一個BeanFactory,那麼是否可以根據這個BeanFactory知道是誰建立他的呢? 這個接口就是幹這個事情的。

public interface HierarchicalBeanFactory extends BeanFactory {

 // 傳回目前工廠的父工廠
 @Nullable
 BeanFactory getParentBeanFactory();

 // 傳回當工廠是否包含這個bean,不從父工廠中去擷取
 boolean containsLocalBean(String name);

}
           

2.3.3 ListableBeanFactory

  • 一個接口可能會有多個實作,每個實作都是一個Bean。是以根據一個類型可能會擷取多個Bean的執行個體。
  • 一個工廠會有很多的Bean,能不能一下擷取工廠所有的Bean呢?

這個工廠名字定義的很有意思,Listable, List 是以大多接口是傳回集合。你不信,你看下面展示。

public interface ListableBeanFactory extends BeanFactory {

 // 是否包含BeanDefinition,BeanDefinition是bean執行個體化的基   // 本資訊。
 boolean containsBeanDefinition(String beanName);

 // 擷取BeanDefinition的數量
 int getBeanDefinitionCount();

 // 擷取BeanDefinition的名稱
 String[] getBeanDefinitionNames();

 // 根據類型,擷取這個類型的所有Bean的名稱
 String[] getBeanNamesForType(ResolvableType type);

 // 根據類型擷取bean的名稱,包含非單例的,允許初始化
 String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit);

 // 根據類型,擷取這個類型的所有Bean的名稱
 String[] getBeanNamesForType(@Nullable Class<?> type);

 // 根據類型擷取bean的名稱,包含非單例的,允許初始化
 String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);

 // 根據類型擷取Bean的字典,key是名稱 value是執行個體
 <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;

 // 根據類型擷取Bean的字典(包含非單例),key是名稱 value是執行個體
 <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
   throws BeansException;

 // 擷取被目前注解修飾的Bean的名稱,隻擷取名稱不執行個體化,支援注解派   // 生的方式
 String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);

 // 擷取被該注解修飾的bean,key是名稱,value是執行個體。
 Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;

 // 擷取目前名稱Bean的,目前注解的資訊
 @Nullable
 <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
   throws NoSuchBeanDefinitionException;

}
           

2.3.4 ConfigurableBeanFactory

這個工廠,是最容易看出他的用途的,名字一個看就是跟配置相關的。

public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {

 // 單例:一個容器隻都存在執行個體
 String SCOPE_SINGLETON = "singleton";

 // 原型:每次getBean一次生成一個執行個體
 String SCOPE_PROTOTYPE = "prototype";

 // 設定他的父工廠
 void setParentBeanFactory(BeanFactory parentBeanFactory) throws IllegalStateException;

 // 設定類加載器以用于加載 bean 類。預設是線程上下文類加載器。
 void setBeanClassLoader(@Nullable ClassLoader beanClassLoader);

 // 傳回此工廠的類加載器以加載 bean 類
 @Nullable
 ClassLoader getBeanClassLoader();

 // 指定用于類型比對目的的臨時 ClassLoader。預設為無
 void setTempClassLoader(@Nullable ClassLoader tempClassLoader);

 // 擷取臨時的類加載器
 @Nullable
 ClassLoader getTempClassLoader();

 // 設定是否緩存 bean 中繼資料,例如給定的 bean 定義(以合并方式)和解析的 bean 類。預設開啟。
 void setCacheBeanMetadata(boolean cacheBeanMetadata);

 // 傳回是否緩存 bean 中繼資料
 boolean isCacheBeanMetadata();

 // bean 定義值中的表達式指定解析政策。
  // 預設是 StandardBeanExpressionResolver。
 void setBeanExpressionResolver(@Nullable BeanExpressionResolver resolver);

 // 擷取解析類型 StandardBeanExpressionResolver
 @Nullable
 BeanExpressionResolver getBeanExpressionResolver();

 // 設定轉換層統一的API,後面有專門章節說這個體系。
 void setConversionService(@Nullable ConversionService conversionService);

 // 擷取轉換API
 @Nullable
 ConversionService getConversionService();

 // 給工廠添加一個屬性設定的注冊器,實際用的不多,但是有必要去了解,後面也會介紹
 void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);

 // 為給定類型的所有屬性注冊給定的自定義屬性編輯器。在工廠配置期間調用。
 void registerCustomEditor(Class<?> requiredType, Class<? extends PropertyEditor> propertyEditorClass);

 // BeanFactory 中注冊的自定義編輯器初始化給定的 PropertyEditorRegistry
 void copyRegisteredEditorsTo(PropertyEditorRegistry registry);

 // 設定類型轉換器
 void setTypeConverter(TypeConverter typeConverter);

 // 擷取類型轉換器
 TypeConverter getTypeConverter();

 // 添加字元串解析器。
 void addEmbeddedValueResolver(StringValueResolver valueResolver);

 // 是否有字元串解析器
 boolean hasEmbeddedValueResolver();

 // 解析資料
 @Nullable
 String resolveEmbeddedValue(String value);

 // 添加一個新的 BeanPostProcessor,它将應用于此工廠建立的 bean。在工廠配置期間調用。
  // 非系統定義的處理器,都可以使用Order進行排序
  // 這是一個非常重要的Bean處理器
 void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);

 // 處理器的個人
 int getBeanPostProcessorCount();

 // 注冊由給定 Scope 實作支援的給定範圍
  // 這裡稍微解釋下什麼是Scope,就比如Session内有效或者是Request内有效
 void registerScope(String scopeName, Scope scope);

 // 傳回所有目前注冊範圍的名稱,不會公開諸如“singleton”和“prototype”之類的内置作用域
 String[] getRegisteredScopeNames();

 // 擷取域的域對象
 @Nullable
 Scope getRegisteredScope(String scopeName);

 // 提供與該工廠相關的安全通路控制上下文。
 AccessControlContext getAccessControlContext();

 // 拷貝當Bean工廠的配置
 void copyConfigurationFrom(ConfigurableBeanFactory otherFactory);

 // 給bean注冊一個别名
 void registerAlias(String beanName, String alias) throws BeanDefinitionStoreException;

 // 解析在此工廠中注冊的所有别名目标名稱和别名,并将給定的 StringValueResolver 應用于它們。
 void resolveAliases(StringValueResolver valueResolver);

 // 傳回給定 bean 名稱的合并 BeanDefinition,如有必要,将子 bean 定義與其父合并。
 BeanDefinition getMergedBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

 // 是否是FactoryBean
 boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException;

 // 設定目前Bean正在建立中。僅供容器内部會使用。
 void setCurrentlyInCreation(String beanName, boolean inCreation);

 // 目前Bean是否建立中
 boolean isCurrentlyInCreation(String beanName);

 // 為給定的 bean 注冊一個依賴 bean
 void registerDependentBean(String beanName, String dependentBeanName);

 // 傳回依賴于指定 bean 的所有 bean 的名稱
 String[] getDependentBeans(String beanName);

 // 擷取目前Bean依賴的Bean
 String[] getDependenciesForBean(String beanName);

 // 銷毀bean
 void destroyBean(String beanName, Object beanInstance);

 // 銷毀目前目标作用域中的指定作用域bean(如果有)
 void destroyScopedBean(String beanName);

 // 銷毀單例
 void destroySingletons();

}

           

2.3.5 AutowireCapableBeanFactory

Autowire是不是看着很熟,提供自動注入的方法。

public interface AutowireCapableBeanFactory extends BeanFactory {

 // 不需要自動裝配
 int AUTOWIRE_NO = 0;

 // 表示按名稱自動裝配 bean 屬性的常量
 int AUTOWIRE_BY_NAME = 1;

 // 按照類型來自動裝配
 int AUTOWIRE_BY_TYPE = 2;

 // 訓示自動裝配可以滿足的最貪婪構造函數的常量
 int AUTOWIRE_CONSTRUCTOR = 3;

 //
 @Deprecated
 int AUTOWIRE_AUTODETECT = 4;

 // 5.1 才有的。初始化現有 bean 執行個體時的“原始執行個體”約定的字尾:附加到完全限定的 bean 類名,例如“com.mypackage.MyClass.ORIGINAL”,以強制傳回給定的執行個體,即沒有代理等。
 String ORIGINAL_INSTANCE_SUFFIX = ".ORIGINAL";


 //-------------------------------------------------------------------------
 // 建立和填充bean 執行個體的方法
 //-------------------------------------------------------------------------

 // 建立bean
 <T> T createBean(Class<T> beanClass) throws BeansException;

 // 自動裝配bean
 void autowireBean(Object existingBean) throws BeansException;

 // 給一個空執行個體,也能進行填充。
 Object configureBean(Object existingBean, String beanName) throws BeansException;


 //-------------------------------------------------------------------------
 // 對 bean 生命周期進行細粒度控制的專用方法
 //-------------------------------------------------------------------------

 
 Object createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;
  
 Object autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;
  
 void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck)
   throws BeansException;
      
 void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException;
  
 Object initializeBean(Object existingBean, String beanName) throws BeansException;
  
 Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
   throws BeansException;
      
 Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
   throws BeansException;
      
 void destroyBean(Object existingBean);

 <T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType) throws BeansException;

 Object resolveBeanByName(String name, DependencyDescriptor descriptor) throws BeansException;

 @Nullable
 Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName) throws BeansException;

 @Nullable
 Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
   @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException;

}

           

2.3.6 ConfigurableListableBeanFactory

看名字大概就能猜出些什麼了,具體接口定義看下面。

public interface ConfigurableListableBeanFactory
  extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory {

 // 自動裝配時候,忽略這些類型
 void ignoreDependencyType(Class<?> type);

 // 自動裝配時候,忽略這些接口
 void ignoreDependencyInterface(Class<?> ifc);

 // 給目前類型,注入指定的執行個體。
 void registerResolvableDependency(Class<?> dependencyType, @Nullable Object autowiredValue);

 // 判斷目前bean是否有資格作為自動裝配的候選者
 boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor)
   throws NoSuchBeanDefinitionException;

 // 傳回指定 bean 的注冊 BeanDefinition
 BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

 // 傳回此工廠管理的所有 bean 名稱
 Iterator<String> getBeanNamesIterator();

 // 清除合并的 bean 定義緩存,通常在更改原始 bean 定義後觸發
 void clearMetadataCache();

 // 當機所有 bean 定義,表示注冊的 bean 定義将不會被修改或進一步後處理
 void freezeConfiguration();

 // 傳回此工廠的 bean 定義是否被當機,即不應該進一步修改或後處理。
 boolean isConfigurationFrozen();

 // 單例初始化方法,非常重要,我們開發中大部分bean初始化就是這個方法調用的哦。
 void preInstantiateSingletons() throws BeansException;

}

           

好了關于工廠的定義已經全部展示了,剩下的都是具體的實作。具體的實作就不單獨拿出來了。下面我們來看Spring中的上下文對象。

三、ApplicationContext 容器上下文

應用上下文,是Spring中最最核心的類,也是功能最強大的類,Spring所有的工具基本都能通過上下文來擷取。

  • 擷取環境變量
  • 擷取Bean工廠
  • 發送容器事件

下面我們看Spring中建構上下文的幾種方式。

3.1 建構上下文

3.1.1 參數化建構

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
           

3.1.2 目錄掃描

掃描 com.acme 包以查找任何 帶@Component注釋的類,并且這些類在容器中注冊為 Spring bean 定義。

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
           

3.2 容器事件

3.2.1 ContextRefreshedEvent 容器重新整理事件

容器啟動的最後一步,發送容器重新整理事件,當收到這個事件的時候,容器就已經準備就緒了,你就可以正常使用了。

  • AbstractApplicationContext#finishRefresh
十天8000字,我總結了這一篇Spring容器管理知識大全

3.2.2 ContextClosedEvent 關閉事件

一旦應用被關閉或者中斷就會觸發容器關閉事件。但是 kill -9 除外, kill 是可以的。這背後的原因,這是linux系統的機制,更多詳細請自行百度。

3.2.3 ContextStartedEvent 啟動事件

ContextStartedEvent 跟前面兩個的事件不同是,必須要顯示觸發,比如下面這樣。

public static void main(String[] args) {
        SpringApplication.run(Application.class,args).start();
 }
           

3.2.4 ContextStoppedEvent 停止事件

ContextStoppedEvent 和 ContextStartedEvent 是一樣的,必須要顯示調用。

public static void main(String[] args) {
        SpringApplication.run(Application.class,args).stop();
 }
           

3.2.5 RequestHandledEvent

當收到http請求時候觸發,此事件僅适用于使用 Spring 的 Web 應用程式DispatcherServlet。

3.2.6 ServletRequestHandledEvent

跟前這一樣,不同的是增加了Servlet的資訊.

十天8000字,我總結了這一篇Spring容器管理知識大全

更多事件相關,請看下一篇,Event專題

四、JavaConfig 配置

在之前Spring的配置都是基于xml方式,當Jdk5之後支援注解後,Spring的配置方式增加了基于注解的配置。

那麼你認為Java代碼注解配置好? 還是xml方式好呢?

我們看下官方的回答:

  • 簡短的回答是“視情況而定”。
  • 長答案是每種方法都有其優點和缺點,通常由開發人員決定哪種政策更适合他們。

由于它們的定義方式,注解方式在其聲明中提供了大量上下文,進而使配置更短、更簡潔。 然而,XML 擅長在不觸及源代碼或重新編譯它們的情況下連接配接元件。一些開發人員更喜歡在源附近進行布線,而另一些開發人員則認為帶注釋的類不再是 POJO,此外,配置變得分散且更難控制。 無論選擇如何,Spring 都可以同時适應這兩種風格,甚至可以将它們混合在一起。

改部分介紹如何在 Java 代碼中使用注解來配置 Spring 容器。它包括以下主題:

4.1 @Configuration 配置類

Spring 新的 Java 配置,的主要使用的是 @Configuration注釋的類。

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
           

前面的AppConfig類等價于下面的 Spring XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
           

4.2 帶@Bean注解的方法

當@Bean方法在沒有用 @Configuration 注解修飾的類中聲明時 ,它們被稱為以“精簡”模式處理。 如下代碼示例。

@Component
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
           

4.3 @Configuration和@Bean的差別

與@Configuration不同的是,使用@Bean方法的模式, 不能聲明 bean 間的依賴關系。這句話怎麼了解的。我們舉一個代碼的例子。

@Component
public class BeanConf {

    @Bean("serverA")
    public ServerA serverA() {
        ServerA serverA = new ServerA("Configuration 方式");
        System.out.println("ServerA:" + serverA.hashCode());
        return serverA;
    }

    @Bean("serverB")
    public ServerB serverB() {
        ServerB serverB = new ServerB();
        ServerA serverA = serverA();
        System.out.println("Method ServerA:" + serverA.hashCode());
        serverB.setServerA(serverA);
        return serverB;
    }
}
           

我們使用 Component 來修飾, ServerA: 這一行,會列印2次,第一次是 @Bean解析Bean時候。第二次是 在 serverB方法調用時候執行。此時ServerB中注入的ServerA并不是被容器管理的Bean。而是調用方法建立的ServerA。

好下面我們看另外一個例子。

@Configuration
public class BeanConf {

    @Bean("serverA")
    public ServerA serverA() {
        ServerA serverA = new ServerA("Configuration 方式");
        System.out.println("ServerA:" + serverA.hashCode());
        return serverA;
    }

    @Bean("serverB")
    public ServerB serverB() {
        ServerB serverB = new ServerB();
        ServerA serverA = serverA();
        System.out.println("Method ServerA:" + serverA.hashCode());
        serverB.setServerA(serverA);
        return serverB;
    }
}
           

與前面不同的是, ServerA: 這一行,會列印1次,就是解析 @Bean的時候。而 serverB()方法中雖然調用了 serverA()方法,但是并不會執行,而是從容器中直接拿到前面解析的Bean。

是以我們得出結論,我們盡量要用 @Configuration 來聲明配置,避免出現意外的問題。

五、基于注解容器配置

5.1 @Required

此注解訓示必須在配置時通過 bean 定義中的顯式屬性值或通過自動裝配來填充受影響的 bean 屬性。如果受影響的 bean 屬性尚未填充,則容器将引發異常。

處理類: RequiredAnnotationBeanPostProcessor

注意: 這種方式已經聲明廢棄了,不過也支援,但是不建議使用。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
           

5.2 @Autowired

聲明注入的,@Autowired 預設不允許為空,即跟 @Required 一樣,如果為空就中斷,但是也允許為空。 如果為空,不想中斷,可以這樣使用 @Autowired(required = false)

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
           

5.3 @Primary

  • Primary翻譯: 主要的

由于按類型自動裝配可能會導緻多個候選者,是以通常需要對選擇過程進行更多控制。實作這一點的一種方法是使用 Spring 的 @Primary注釋。@Primary: 當多個 bean 是自動裝配到單值依賴項的候選對象時,應該優先考慮特定的 bean。如果候選中恰好存在一個主 bean,則它将成為自動裝配的值。

如下,MovieCatalog類型有兩個Bean。

@Configuration
public class MovieConfiguration {

    @Bean("MovieCatalog1")
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean("MovieCatalog2")
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}
           

當要進行注入時候就會報錯,因為根據類型發現了兩個備選的Bean。而這種情況的解決辦法就是其中一個使用 @Primary 來修飾。此時容器就知道你到底要注冊那個了,當被 @Primary 修飾的Bean會被正确注入。

此時可能有朋友會問,如果兩個一樣類型的Bean都用 @Primary 來修飾呢? 結果就是會報錯。如下。

No qualifying bean of type 'learn.spring.service.ServerA' available: more than one 'primary' bean found among candidates: [serverA1, serverA2]
           

5.4 @Qualifier

@Primary當可以确定一個主要候選者時,是一種通過類型使用多個執行個體的自動裝配的有效方法。當您需要對選擇過程進行更多控制時,可以使用 Spring 的@Qualifier注解。您可以将限定符值與特定參數相關聯,縮小類型比對的範圍,以便為每個參數選擇特定的 bean。

@Configuration
public class MovieConfiguration {

    @Bean("main")
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}
           

一個最簡單的解釋就是 @Autowired + @Qualifier = @Resource。

5.5 CustomAutowireConfigurer

前面我們可以通過 @Qualifier 實作根據名字的注入, CustomAutowireConfigurer 允許我們自定義一個注解, 具備和 @Qualifier 一樣的功能。

首先我們聲明一個注解,保持和@Qualifier一樣的結構

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ConditionAutowire {
    String value() default "";
}
           

然後使用CustomAutowireConfigurer來,配置我們自定義的注解。

/**
     * 自定義一個注入工具
     *
     * @return 注入工具
     */
    @Bean
    public CustomAutowireConfigurer customAutowireConfigurer() {
        CustomAutowireConfigurer customAutowireConfigurer = new CustomAutowireConfigurer();
        customAutowireConfigurer.setCustomQualifierTypes(Collections.singleton(ConditionAutowire.class));
        return customAutowireConfigurer;
    }
           

這樣我們就能使用下面的代碼了。

@Component
public class ServerB {

    ServerA serverA;


    @Autowired
    // @Qualifier("serverAA") 與下面代碼等價。
    @ConditionAutowire("serverAA")
    public void setServerA(ServerA serverA) {
        this.serverA = serverA;
    }
}    
           

5.6 @Resource

Spring 還通過在字段或 bean 屬性設定器方法上使用 JSR-250@Resource注釋 ( )來支援注入。javax.annotation.Resource這是 Java EE 中的常見模式:例如,在 JSF 管理的 bean 和 JAX-WS 端點中。Spring 也支援 Spring 管理的對象的這種模式。

@Resource采用名稱屬性。預設情況下,Spring 将該值解釋為要注入的 bean 名稱。換句話說,它遵循按名稱語義,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
           

如果沒有明确指定名稱,則預設名稱派生自字段名稱或 setter 方法。如果是字段,則采用字段名稱。對于 setter 方法,它采用 bean 屬性名稱。以下示例将把名為 bean 的 beanmovieFinder注入到它的 setter 方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
           

5.7 @Value

@Value通常用于注入外部屬性

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}
           

application.properties檔案,添加上一下配置

catalog.name=MovieCatalog
           

5.7.1 預設值

  • @Value("${catalog.name:defaultCatalog}")
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}
           

5.7.1 支援SpringEL 表達式

當@Value包含SpEL表達式時,該值将在運作時動态計算,如以下示例所示:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}
           

SpEL 還支援使用更複雜的資料結構:

  • 注意如果使用EL表達式,就不是$而是#
@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}
           

5.8 初始化 & 銷毀方法

  • @PostConstruct
  • @PreDestroy

處理類: InitDestroyAnnotationBeanPostProcessor

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // 初始化執行
    }

    @PreDestroy
    public void clearMovieCache() {
        // Bean銷毀執行
    }
}
           

可能會有人問

  • 不是還有InitializingBean初始化和DisposableBean接口能實作初始化和銷毀方法嗎?
public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}
public interface DisposableBean {
    void destroy() throws Exception;
}
           
  • 不是還可以通過 @Bean(initMethod = "init",destroyMethod = "destroy") 來聲明嗎?

是的當然都可以,不過這也是有執行順序的,順序如下。

十天8000字,我總結了這一篇Spring容器管理知識大全

5.9 @Scope

這個注解平時接觸的都很少,但是其實我們都在用,因為如果不顯示聲明,預設就是 @Scope("singleton")

這個怎麼了解呢? 比如在Spring中預設都是單例 singleton,這就意味着就是說在容器不關閉的情況下,不管你調用了幾次都是同一個執行個體。如果我們想讓每個Thread拿到自己的執行個體呢? 有沒有辦法呢?

當然有,如下我們定一個Thread範圍的Bean, 首先給工廠定義自己的域範圍。

@Component
public class BeanFactoryConf implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope("thread", new SimpleThreadScope());
    }

    @Bean
    @Scope("thread")
    public ThreadScopeBean threadScopeBean() {
        return new ThreadScopeBean(Thread.currentThread().getName());
    }

    public static class ThreadScopeBean {

        String name;

        public ThreadScopeBean(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

}
           

然後使用多個線程來擷取這個Bean,最終我們會發現,每個線程得到的執行個體都是不一樣的。符合Thread這個域的範圍。

public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
        ServerB bean = run.getBean(ServerB.class);
        System.out.println(bean);
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                BeanFactoryConf.ThreadScopeBean threadScope = run.getBean(BeanFactoryConf.ThreadScopeBean.class);
                // thread-scope-1
                // thread-scope-2
                // thread-scope-0
                System.out.println(threadScope);
            }, "thread-scope-" + i).start();
        }
    }
           

注意: 上面這個例子,必須每次從容器中重新擷取Bean才會生效。

當然這裡是Thread範圍,其實還有Session範圍和request範圍,這兩個是我們使用最多的。他們兩個是如何實作的呢? 大家可以思考下,其實也很簡單。就是對工具類和ThreadLocal的利用。有知道原理的,可以下面評論。

5.9.1 HttpServletRequest 注入

這裡解釋一個經常被弄混淆概念,就是我們知道我們在容器中注入一個 HttpServletRequest 這個類, HttpServletRequest 不是一個 Bean, 為什麼能注入呢? 每次在使用的時候,都會擷取目前的請求對象。他是如何實作的呢? 他不是Scope來實作的。而是通過。下面 這兩個行代碼一起來實作的。

  • beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()) 這一行的意思是,當發現你要注入的是SCOPE_REQUEST,時候會調用RequestScope@getObject來執行個體化。這個類不是單例不會被容器儲存,也不是原型不會每次都來重新建立。
  • beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()) 的意思是,當這個類被注入到其他類的時候,要進行代理。
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
   @Nullable ServletContext sc) {

  beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
  beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
    ...
 }
           

在進行自動注入的時候,如果發現執行個體是一個 ObjectFactory 就會生成代理類。

public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
  if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
      // 這裡擷取到RequestObjectFactory
   ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
   if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
    autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
      new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
   }
   else {
    return factory.getObject();
   }
  }
  return autowiringValue;
 }

           

然後代理類中這樣處理,在執行每個方法的時候,都從新擷取 ObjectFactory#getObject()。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   String methodName = method.getName();
   if (methodName.equals("equals")) {
    // Only consider equal when proxies are identical.
    return (proxy == args[0]);
   }
   else if (methodName.equals("hashCode")) {
    // Use hashCode of proxy.
    return System.identityHashCode(proxy);
   }
   else if (methodName.equals("toString")) {
    return this.objectFactory.toString();
   }
   try {
        // 每次執行方法,都從新擷取objectFactory.getObject()
        // RequestObjectFactory中是使用ThreadLocal的方式來實作。
    return method.invoke(this.objectFactory.getObject(), args);
   }
   catch (InvocationTargetException ex) {
    throw ex.getTargetException();
   }
  }
           

5.10 @Import

@Import 注解允許 @Bean 從另一個配置類加載定義,如以下示例所示:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}
           

現在,不需要同時指定ConfigA.class和ConfigB.class在執行個體化上下文時,隻ConfigB需要顯式提供,如以下示例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}
           
從 Spring Framework 4.2 開始,@Import還支援對正常元件類的引用,類似于AnnotationConfigApplicationContext.register方法。如果您想通過使用一些配置類作為入口點來顯式定義所有元件來避免元件掃描,這将特别有用。

這裡我們定義一個注解,使用Import修飾,這樣當使用這個注解時候,就會自動去注冊 DubboComponentScanRegistrar 到容器,然後去處理些dubbo元件掃描的邏輯。然後就可以你在DubboComponentScanRegistrar中來擷取到DubboComponentScan注解的資訊。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
   String[] value() default {};
}

public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {

   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 擷取DubboComponentScan注解中配置要掃描的目錄
        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
        // 掃描上面指定的目錄,生成BeanDefinition通過registry去注冊。
        registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
        
        registerReferenceAnnotationBeanPostProcessor(registry);

    }
}
           

5.11 @Profile

Bean 定義配置檔案在核心容器中提供了一種機制,允許在不同環境中注冊不同的 bean。“環境”這個詞對不同的使用者可能意味着不同的東西,這個功能可以幫助許多用例,包括:

  • 在開發中處理記憶體中的資料源,而不是在 QA 或生産中從 JNDI 中查找相同的資料源。
  • 僅在将應用程式部署到性能環境時才注冊監控基礎架構。
  • 為客戶 A 和客戶 B 部署注冊定制的 bean 實作。
@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
           

5.11.1 自定義環境注解

可以将 @Profile 其用作元注釋以建立自定義組合注釋。以下示例定義了一個自定義 @Production 注釋,您可以将其用作 的替代品 @Profile("production")

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
           

5.11.2 激活環境

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
           

此外,您還可以通過 spring.profiles.active 屬性以聲明方式激活配置檔案

以聲明方式,spring.profiles.active 可以接受以逗号分隔的配置檔案名稱清單,如以下示例所示:

-Dspring.profiles.active="profile1,profile2"
           

六、Aware

這個比較簡單,當你看到實作了Aware結尾的接口,Spring都會給你自動給你注入對應的Spring種内置的元件。這個怎麼了解呢,看下面。

十天8000字,我總結了這一篇Spring容器管理知識大全

6.1 BeanFactoryAware

擷取 BeanFactory

public interface BeanFactoryAware extends Aware {
 void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}
           

6.2 BeanNameAware

擷取 Bean 的名稱

public interface BeanNameAware extends Aware {
 void setBeanName(String name);
}
           

6.3 MessageSourceAware

擷取國際化對象 MessageSource

public interface MessageSourceAware extends Aware {
 void setMessageSource(MessageSource messageSource);
}
           

6.4 ApplicationContextAware

擷取容器上下文 ApplicationContext

public interface ApplicationContextAware extends Aware {
 void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
           

6.5 ApplicationEventPublisherAware

擷取事件發送者 ApplicationEventPublisher

public interface ApplicationEventPublisherAware extends Aware {
 void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);
}
           

6.6 ResourceLoaderAware

擷取資源加載器 ResourceLoader

public interface ResourceLoaderAware extends Aware {
 void setResourceLoader(ResourceLoader resourceLoader);
}
           

6.7 ServletConfigAware

擷取 ServletConfig

public interface ServletConfigAware extends Aware {
 void setServletConfig(ServletConfig servletConfig);
}
           

6.8 ServletContextAware

public interface ServletContextAware extends Aware {
 void setServletContext(ServletContext servletContext);
}
           

七、生成候選元件的索引

雖然類路徑掃描非常快,但可以通過在編譯時建立靜态候選清單來提高大型應用程式的啟動性能。在這種模式下,作為元件掃描目标的所有子產品都必須使用這種機制。

當 ApplicationContext檢測到這樣的索引時,它會自動使用它而不是掃描類路徑,這樣能提高速度。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.3.22</version>
        <optional>true</optional>
    </dependency>
</dependencies>
           

當引用之後,再編譯期間生成配置檔案。

十天8000字,我總結了這一篇Spring容器管理知識大全

這個的原理,其實就跟lombok類似,使用到的都是 APT 技術,如果感興趣的話,可以看我這篇文章。

【lombok原理】無聊的周末一個人手寫一個lombok

十天8000字,我總結了這一篇Spring容器管理知識大全

都看到這裡了,最後如果這篇文章,對你有所幫助,請點個關注,交個朋友。

十天8000字,我總結了這一篇Spring容器管理知識大全