寫在前面
當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是個啥?我們點進去看一下。
package org.springframework.context.annotation;
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()方法中添加相應的代碼片段,如下所示。
@Test
public void 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
binghe001
binghe002
給容器中添加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()方法中,如下所示。
@Test
public void 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
person
binghe001
binghe002
給容器中添加Person....
{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18), binghe002=Person(name=binghe002, age=20)}
由于我使用的操作系統是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;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @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
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;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author binghe
* @version 1.0.0
* @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})
@Bean("binghe001")
public Person person01(){
return new Person("binghe001", 18);
}
@Conditional({LinuxCondition.class})
@Bean("binghe002")
public Person person02(){
return new Person("binghe002", 20);
}
此時,我們再次運行SpringBeanTest類中的testAnnotationConfig6()方法,輸出的結果信息如下所示。
Windows 10
person
binghe001
給容器中添加Person....
{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注冊。
好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!
項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation