【String注解驅動開發】如何按照條件向Spring容器中注冊bean?這次我懂了!!
寫在前面
當bean是單執行個體,并且沒有設定懶加載時,Spring容器啟動時,就會執行個體化bean,并将bean注冊到IOC容器中,以後每次從IOC容器中擷取bean時,直接傳回IOC容器中的bean,不再建立新的bean。
如果bean是單執行個體,并且使用@Lazy注解設定了懶加載,則Spring容器啟動時,不會執行個體化bean,也不會将bean注冊到IOC容器中,隻有第一次擷取bean的時候,才會執行個體化bean,并且将bean注冊到IOC容器中。
如果bean是多執行個體,則Spring容器啟動時,不會執行個體化bean,也不會将bean注冊到IOC容器中,以後每次從IOC容器中擷取bean時,都會建立一個新的bean傳回。
Spring支援按照條件向IOC容器中注冊bean,滿足條件的bean就會被注冊到IOC容器中,不滿足條件的bean就不會被注冊到IOC容器中。接下來,我們就一起來探讨Spring中如何實作按照條件向IOC容器中注冊bean。
項目工程源碼已經送出到GitHub:
https://github.com/sunshinelyz/spring-annotation@Conditional注解概述
@Conditional注解可以按照一定的條件進行判斷,滿足條件向容器中注冊bean,不滿足條件就不向容器中注冊bean。
@Conditional注解是由 SpringFramework 提供的一個注解,位于 org.springframework.context.annotation 包内,定義如下。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
從@Conditional注解的源碼來看,@Conditional注解可以添加到類上,也可以添加到方法上。在@Conditional注解中,存在一個Condition類型或者其子類型的Class對象數組,Condition是個啥?我們點進去看一下。
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
可以看到,Condition是一個函數式接口,對于函數式接口不了解的同學可以參見【Java8新特性】中的《【Java8新特性】還沒搞懂函數式接口?趕快過來看看吧!》一文。也可以直接檢視《Java8新特性專欄》來系統學習Java8的新特性。
是以,我們使用@Conditional注解時,需要一個類實作Spring提供的Condition接口,它會比對@Conditional所符合的方法,然後我們可以使用我們在@Conditional注解中定義的類來檢查。
@Conditional注解的使用場景如下所示。
可以作為類級别的注解直接或者間接的與@Component相關聯,包括@Configuration類;
可以作為元注解,用于自動編寫構造性注解;
作為方法級别的注解,作用在任何@Bean方法上。
向Spring容器注冊bean
不帶條件注冊bean
我們在PersonConfig2類中新增person01()方法和person02()方法,并為兩個方法添加@Bean注解,如下所示。
@Bean("binghe001")
public Person person01(){
return new Person("binghe001", 18);
@Bean("binghe002")
public Person person02(){
return new Person("binghe002", 20);
那麼,這兩個bean預設是否會被注冊到Spring容器中呢,我們建立一個測試用例來測試一下。在SpringBeanTest類中建立testAnnotationConfig6()方法,如下所示。
@Test
public void testAnnotationConfig6(){
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);
我們運作testAnnotationConfig6()方法,輸出的結果資訊如下所示。
person
binghe001
binghe002
從輸出結果可以看出,同時輸出了binghe001和binghe002。說明預設情況下,Spring容器會将單執行個體并且非懶加載的bean注冊到IOC容器中。
接下來,我們再輸出bean的名稱和bean執行個體對象資訊,此時我們在testAnnotationConfig6()方法中添加相應的代碼片段,如下所示。
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);
Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);
再次運作SpringBeanTest類中的testAnnotationConfig6()方法,輸出結果如下所示。
給容器中添加Person....
{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18), binghe002=Person(name=binghe002, age=20)}
可以看到,輸出了注冊到容器的bean。
帶條件注冊bean
現在,我們就要提出新的需求了,比如,如果目前作業系統是Windows作業系統,則向Spring容器中注冊binghe001;如果目前作業系統是Linux作業系統,則向Spring容器中注冊binghe002。此時,我們就需要使用@Conditional注解了。
這裡,有小夥伴可能會問:如何擷取作業系統的類型呢,别急,這個問題很簡單,我們繼續向下看。
使用Spring的ApplicationContext接口就能夠擷取到目前作業系統的類型,如下所示。
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);
我們将上述代碼整合到SpringBeanTest類中的testAnnotationConfig6()方法中,如下所示。
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);
Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);
接下來,我們運作SpringBeanTest類中的testAnnotationConfig6()方法,輸出的結果資訊如下所示。
Windows 10
由于我使用的作業系統是Windows 10作業系統,是以在結果資訊中輸出了Windows 10。
到這裡,我們成功擷取到了作業系統的類型,接下來,就可以實作:如果目前作業系統是Windows作業系統,則向Spring容器中注冊binghe001;如果目前作業系統是Linux作業系統,則向Spring容器中注冊binghe002的需求了。此時,我們就需要借助Spring的@Conditional注解來實作了。
要想使用@Conditional注解,我們需要實作Condition接口來為@Conditional注解設定條件,是以,這裡,我們建立了兩個實作Condition接口的類,分别為WindowsCondition和LinuxCondition,如下所示。
WindowsCondition
package io.mykit.spring.plugins.register.condition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
/**
- @author binghe
- @version 1.0.0
-
@description Windows條件,判斷作業系統是否是Windows
*/
public class WindowsCondition implements Condition {
/**
* ConditionContext:判斷條件使用的上下文環境
* AnnotatedTypeMetadata:注釋資訊
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//判斷是否是Linux系統
//1.擷取到IOC容器使用的BeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2.擷取類加載器
ClassLoader classLoader = context.getClassLoader();
//3.擷取目前的環境資訊
Environment environment = context.getEnvironment();
//4.擷取bean定義的注冊類,我們可以通過BeanDefinitionRegistry對象檢視
//Spring容器中注冊了哪些bean,也可以通過BeanDefinitionRegistry對象向
//Spring容器中注冊bean,移除bean,檢視bean的定義,檢視是否包含某個bean的定義
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
return property.contains("Windows");
}
LinuxCondition
- @description Linux條件,判斷作業系統是否是Linux
public class LinuxCondition implements Condition {
/**
* ConditionContext:判斷條件使用的上下文環境
* AnnotatedTypeMetadata:注釋資訊
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//判斷是否是Linux系統
//1.擷取到IOC容器使用的BeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2.擷取類加載器
ClassLoader classLoader = context.getClassLoader();
//3.擷取目前的環境資訊
Environment environment = context.getEnvironment();
//4.擷取bean定義的注冊類,我們可以通過BeanDefinitionRegistry對象檢視
//Spring容器中注冊了哪些bean,也可以通過BeanDefinitionRegistry對象向
//Spring容器中注冊bean,移除bean,檢視bean的定義,檢視是否包含某個bean的定義
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
return property.contains("linux");
}
接下來,我們就需要在PersonConfig2類中使用@Conditional注解添加條件了。添加注解後的方法如下所示。
@Conditional({WindowsCondition.class})
return new Person("binghe001", 18);
@Conditional({LinuxCondition.class})
return new Person("binghe002", 20);
此時,我們再次運作SpringBeanTest類中的testAnnotationConfig6()方法,輸出的結果資訊如下所示。
{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18)}
可以看到,輸出結果中不再含有名稱為binghe002的bean了,說明程式中檢測到目前作業系統為Windows10,沒有向Spring容器中注冊名稱為binghe002的bean。
@Conditional注解也可以标注在類上,标注在類上含義為:滿足目前條件,這個類中配置的所有bean注冊才能生效,大家可以自行驗證@Conditional注解标注在類上的情況
@Conditional的擴充注解
@ConditionalOnBean:僅僅在目前上下文中存在某個對象時,才會執行個體化一個Bean。
@ConditionalOnClass:某個class位于類路徑上,才會執行個體化一個Bean。
@ConditionalOnExpression:當表達式為true的時候,才會執行個體化一個Bean。
@ConditionalOnMissingBean:僅僅在目前上下文中不存在某個對象時,才會執行個體化一個Bean。
@ConditionalOnMissingClass:某個class類路徑上不存在的時候,才會執行個體化一個Bean。
@ConditionalOnNotWebApplication:不是web應用,才會執行個體化一個Bean。
@ConditionalOnBean:當容器中有指定Bean的條件下進行執行個體化。
@ConditionalOnMissingBean:當容器裡沒有指定Bean的條件下進行執行個體化。
@ConditionalOnClass:當classpath類路徑下有指定類的條件下進行執行個體化。
@ConditionalOnMissingClass:當類路徑下沒有指定類的條件下進行執行個體化。
@ConditionalOnWebApplication:當項目是一個Web項目時進行執行個體化。
@ConditionalOnNotWebApplication:當項目不是一個Web項目時進行執行個體化。
@ConditionalOnProperty:當指定的屬性有指定的值時進行執行個體化。
@ConditionalOnExpression:基于SpEL表達式的條件判斷。
@ConditionalOnJava:當JVM版本為指定的版本範圍時觸發執行個體化。
@ConditionalOnResource:當類路徑下有指定的資源時觸發執行個體化。
@ConditionalOnJndi:在JNDI存在的條件下觸發執行個體化。
@ConditionalOnSingleCandidate:當指定的Bean在容器中隻有一個,或者有多個但是指定了首選的Bean時觸發執行個體化。
@Conditional 與@Profile 的對比
Spring3.0 也有一些和@Conditional 相似的注解,它們是Spring SPEL 表達式和Spring Profiles 注解 Spring4.0的@Conditional 注解要比@Profile 注解更加進階。@Profile 注解用來加載應用程式的環境。@Profile注解僅限于根據預定義屬性編寫條件檢查。 @Conditional注釋則沒有此限制。
Spring中的@Profile 和 @Conditional 注解用來檢查"If…then…else"的語義。然而,Spring4 @Conditional是@Profile 注解的更通用法。
Spring 3中的 @Profile僅用于編寫基于Environment變量的條件檢查。 配置檔案可用于基于環境加載應用程式配置。
Spring 4 @Conditional注解允許開發人員為條件檢查定義使用者定義的政策。 @Conditional可用于條件bean注冊。
好了,咱們今天就聊到這兒吧!别忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!
寫在最後
如果覺得文章對你有點幫助,請微信搜尋并關注「 冰河技術 」微信公衆号,跟冰河學習Spring注解驅動開發。公衆号回複“spring注解”關鍵字,領取Spring注解驅動開發核心知識圖,讓Spring注解驅動開發不再迷茫。
參考:
https://www.cnblogs.com/cxuanBlog/p/10960575.html原文位址
https://www.cnblogs.com/binghe001/p/13084108.html