前言:
@Conditional是Spring4新提供的注解,它的作用是按照一定的條件進行判斷,滿足條件給容器注冊bean。
@Conditional的定義:
//此注解可以标注在類和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
從代碼中可以看到,需要傳入一個Class數組,并且需要繼承Condition接口:
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
Condition是個接口,需要實作matches方法,傳回true則注入bean,false則不注入。
示例:
首先,建立Person類:
public class Person {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
建立BeanConfig類,用于配置兩個Person執行個體并注入,一個是比爾蓋茨,一個是林納斯。
@Configuration
public class BeanConfig {
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
接着寫一個測試類進行驗證這兩個Bean是否注入成功。
public class ConditionalTest {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
@Test
public void test1(){
Map<String, Person> map = applicationContext.getBeansOfType(Person.class);
System.out.println(map);
}
}
運作,輸出結果是這樣的,兩個Person執行個體被注入進容器。
這是一個簡單的例子,現在問題來了,如果我想根據目前作業系統來注入Person執行個體,windows下注入bill,linux下注入linus,怎麼實作呢?
這就需要我們用到@Conditional注解了,前言中提到,需要實作Condition接口,并重寫方法來自定義match規則。
首先,建立一個WindowsCondition類:
public class WindowsCondition implements Condition {
/**
* @param conditionContext:判斷條件能使用的上下文環境
* @param annotatedTypeMetadata:注解所在位置的注釋資訊
* */
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//擷取ioc使用的beanFactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//擷取類加載器
ClassLoader classLoader = conditionContext.getClassLoader();
//擷取目前環境資訊
Environment environment = conditionContext.getEnvironment();
//擷取bean定義的注冊類
BeanDefinitionRegistry registry = conditionContext.getRegistry();
//獲得目前系統名
String property = environment.getProperty("os.name");
//包含Windows則說明是windows系統,傳回true
if (property.contains("Windows")){
return true;
}
return false;
}
}
matches方法的兩個參數的意思在注釋中講述了,值得一提的是,conditionContext提供了多種方法,友善擷取各種資訊,也是SpringBoot中 @ConditonalOnXX注解多樣擴充的基礎。
接着,建立LinuxCondition類:
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
String property = environment.getProperty("os.name");
if (property.contains("Linux")){
return true;
}
return false;
}
}
接着就是使用這兩個類了,因為此注解可以标注在方法上和類上,是以分開測試:
标注在方法上:
修改BeanConfig:
@Configuration
public class BeanConfig {
//隻有一個類時,大括号可以省略
//如果WindowsCondition的實作方法傳回true,則注入這個bean
@Conditional({WindowsCondition.class})
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
//如果LinuxCondition的實作方法傳回true,則注入這個bean
@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
修改測試方法,使其可以列印目前系統名:
@Test
public void test1(){
String osName = applicationContext.getEnvironment().getProperty("os.name");
System.out.println("目前系統為:" + osName);
Map<String, Person> map = applicationContext.getBeansOfType(Person.class);
System.out.println(map);
}
運作結果如下:
我是運作在windows上的是以隻注入了bill,嗯,沒毛病。
接着實驗linux下的情況,不能運作在linux下,但可以修改運作時參數:
修改後啟動測試方法:
一個方法隻能注入一個bean執行個體,是以@Conditional标注在方法上隻能控制一個bean執行個體是否注入。
标注在類上:
一個類中可以注入很多執行個體,@Conditional标注在類上就決定了一批bean是否注入。
我們試一下,将BeanConfig改寫,這時,如果WindowsCondition傳回true,則兩個Person執行個體将被注入(注意:上一個測試将os.name改為linux,這是我将把這個參數去掉):
@Conditional({WindowsCondition.class})
@Configuration
public class BeanConfig {
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
結果兩個執行個體都被注入:
如果将類上的WindowsCondition.class改為LinuxCondition.class,結果應該可以猜到:
結果就是空的,類中所有bean都沒有注入。
多個條件類:
前言中說,@Conditional注解傳入的是一個Class數組,存在多種條件類的情況。
這種情況貌似判斷難度加深了,測試一波,新增新的條件類,實作的matches傳回false(這種寫死傳回false的方法純屬測試用,沒有實際意義O(∩_∩)O)
public class ObstinateCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return false;
}
}
BeanConfig修改一下:
@Conditional({WindowsCondition.class,ObstinateCondition.class})
@Configuration
public class BeanConfig {
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
結果:
現在如果将ObstinateCondition的matches方法傳回值改成true,兩個bean就被注入進容器:
結論得:
第一個條件類實作的方法傳回true,第二個傳回false,則結果false,不注入進容器。
第一個條件類實作的方法傳回true,第二個傳回true,則結果true,注入進容器中。
繼@Conditional注解後,又基于此注解推出了很多派生注解,比如@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnExpression、@ConditionalOnClass......動态注入bean變得更友善了。本篇将講解@ConditionalOnBean注解。
配置類中有兩個Computer類的bean,一個是筆記本電腦,一個是備用電腦。如果目前容器中已經有電腦bean了,就不注入備用電腦,如果沒有,則注入備用電腦,這裡需要使用到@ConditionalOnMissingBean。
@Configuration
public class BeanConfig {
@Bean(name = "notebookPC")
public Computer computer1(){
return new Computer("筆記本電腦");
}
@ConditionalOnMissingBean(Computer.class)
@Bean("reservePC")
public Computer computer2(){
return new Computer("備用電腦");
}
}
這個注解就實作了功能,這個@ConditionalOnMissingBean為我們做了什麼呢?我們來一探究竟.。
一探究竟:
首先,來看@ConditionalOnMissingBean的聲明:
//可以标注在類和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//使用了@Conditional注解,條件類是OnBeanCondition
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
Class<?>[] value() default {};
String[] type() default {};
Class<?>[] ignored() default {};
String[] ignoredType() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
}
這時候,我們就看到了我們熟悉的@Conditional注解,OnBeanCondition作為條件類。
OnBeanCondition類的聲明:
//定義帶注釋的元件的排序順序,2147483647即為預設值
@Order(2147483647)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {
它繼承了SpringBootCondition類,OnBeanCondition類中沒有matches方法,而SpringBootCondition類中有實作matches方法。OnBeanCondition還實作了ConfigurationCondition,ConfigurationCondition接口不熟悉的讀者可以到Spring ConfigurationCondition接口詳解 了解接口。OnBeanCondition類重寫了getConfigurationPhase()方法,表示在注冊bean的時候注解生效:
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
就從matches方法開始:
//SpringBootCondition類中的matches方法
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//擷取目前的類名或者方法名(由标注的位置決定)
String classOrMethodName = getClassOrMethodName(metadata);
try {
//關鍵代碼:這裡就會判斷出結果
ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
//存入日志
this.logOutcome(classOrMethodName, outcome);
//存入記錄
this.recordEvaluation(context, classOrMethodName, outcome);
//最後傳回ConditionOutcome的isMatch就是傳回boolean類型結果
return outcome.isMatch();
} catch (NoClassDefFoundError var5) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", var5);
} catch (RuntimeException var6) {
throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
}
}
關鍵代碼在OnBeanCondition的getMatchOutcome方法上:
/**
* 獲得判斷結果的方法,ConditionOutcome類中存着boolean類型的結果
*/
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
//傳回一個新的ConditionMessage
ConditionMessage matchMessage = ConditionMessage.empty();
OnBeanCondition.BeanSearchSpec spec;
List matching;
//這是metadata會調用isAnnotated方法判斷目前标注的注解是不是ConditionalOnMissingBean
//其實@ConditionalOnBean、@ConditionalOnMissingBean和@ConditionalOnSingleCandidate都是使用這個條件類,是以這裡做判斷
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnBean.class);
matching = this.getMatchingBeans(context, spec);
if (matching.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnBean.class, new Object[]{spec}).didNotFind("any beans").atAll());
}
matchMessage = matchMessage.andCondition(ConditionalOnBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching);
}
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
OnBeanCondition.BeanSearchSpec spec = new OnBeanCondition.SingleCandidateBeanSearchSpec(context, metadata, ConditionalOnSingleCandidate.class);
matching = this.getMatchingBeans(context, spec);
if (matching.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).didNotFind("any beans").atAll());
}
if (!this.hasSingleAutowireCandidate(context.getBeanFactory(), matching, spec.getStrategy() == SearchStrategy.ALL)) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).didNotFind("a primary bean from beans").items(Style.QUOTE, matching));
}
matchMessage = matchMessage.andCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).found("a primary bean from beans").items(Style.QUOTE, matching);
}
//如果目前注入的bean是@ConditionalOnMissingBean
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
//傳回一個spec(說明),這裡的spec規定了搜尋的内容,比如搜尋政策、需要搜尋的類名......
spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
//主要的搜尋實作在這個方法裡,最後傳回一個list
matching = this.getMatchingBeans(context, spec);
//判斷搜尋出來的結果
if (!matching.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, new Object[]{spec}).didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
}
spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnBean.class);
這句中,相當于從内部類中将标注@ConditionalOnMissingBean注解時的屬性都取出來:
BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata, Class<?> annotationType) {
this.annotationType = annotationType;
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
//将attributes這個map中的資料放到對應的list成員變量中
this.collect(attributes, "name", this.names);
this.collect(attributes, "value", this.types);
this.collect(attributes, "type", this.types);
this.collect(attributes, "annotation", this.annotations);
this.collect(attributes, "ignored", this.ignoredTypes);
this.collect(attributes, "ignoredType", this.ignoredTypes);
this.strategy = (SearchStrategy)metadata.getAnnotationAttributes(annotationType.getName()).get("search");
OnBeanCondition.BeanTypeDeductionException deductionException = null;
try {
if (this.types.isEmpty() && this.names.isEmpty()) {
this.addDeducedBeanType(context, metadata, this.types);
}
} catch (OnBeanCondition.BeanTypeDeductionException var7) {
deductionException = var7;
}
this.validate(deductionException);
}
//驗證的方法
protected void validate(OnBeanCondition.BeanTypeDeductionException ex) {
if (!this.hasAtLeastOne(this.types, this.names, this.annotations)) {
String message = this.annotationName() + " did not specify a bean using type, name or annotation";
if (ex == null) {
throw new IllegalStateException(message);
} else {
throw new IllegalStateException(message + " and the attempt to deduce the bean's type failed", ex);
}
}
}
看一下OnBeanCondition類中的getMatchingBeans方法,裡面有用到搜尋政策,詳見搜尋政策介紹
private List<String> getMatchingBeans(ConditionContext context, OnBeanCondition.BeanSearchSpec beans) {
//獲得目前bean工廠
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//判斷目前的搜尋政策是否是PARENTS或者ANCESTORS,預設是ALL
if (beans.getStrategy() == SearchStrategy.PARENTS || beans.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS");
//如果是PARENTS或者ANCESTORS,目前bean工廠就用父工廠
beanFactory = (ConfigurableListableBeanFactory)parent;
}
if (beanFactory == null) {
return Collections.emptyList();
} else {
List<String> beanNames = new ArrayList();
//如果目前搜尋政策等于CURRENT,為true
boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
//這裡的type就是需要查找的bean的類型
//下面,會從屬性中找bean
Iterator var6 = beans.getTypes().iterator();
String beanName;
while(var6.hasNext()) {
beanName = (String)var6.next();
//如果找到了類型,接下來就是根據類型找bean的執行個體名,找示例名的方法在下方,實際上就是一個getNamesForType
beanNames.addAll(this.getBeanNamesForType(beanFactory, beanName, context.getClassLoader(), considerHierarchy));
}
var6 = beans.getIgnoredTypes().iterator();
while(var6.hasNext()) {
beanName = (String)var6.next();
beanNames.removeAll(this.getBeanNamesForType(beanFactory, beanName, context.getClassLoader(), considerHierarchy));
}
var6 = beans.getAnnotations().iterator();
while(var6.hasNext()) {
beanName = (String)var6.next();
beanNames.addAll(Arrays.asList(this.getBeanNamesForAnnotation(beanFactory, beanName, context.getClassLoader(), considerHierarchy)));
}
var6 = beans.getNames().iterator();
while(var6.hasNext()) {
beanName = (String)var6.next();
if (this.containsBean(beanFactory, beanName, considerHierarchy)) {
beanNames.add(beanName);
}
}
//将存放bean執行個體名的list傳回
return beanNames;
}
}
//根據類型擷取bean的name
private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory, String type, ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
try {
Set<String> result = new LinkedHashSet();
this.collectBeanNamesForType(result, beanFactory, ClassUtils.forName(type, classLoader), considerHierarchy);
return result;
} catch (ClassNotFoundException var6) {
return Collections.emptySet();
} catch (NoClassDefFoundError var7) {
return Collections.emptySet();
}
}
private void collectBeanNamesForType(Set<String> result, ListableBeanFactory beanFactory, Class<?> type, boolean considerHierarchy) {
result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type));
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
BeanFactory parent = ((HierarchicalBeanFactory)beanFactory).getParentBeanFactory();
if (parent instanceof ListableBeanFactory) {
this.collectBeanNamesForType(result, (ListableBeanFactory)parent, type, considerHierarchy);
}
}
}
找完bean了之後,回到剛才的代碼裡:
//如果目前注入的bean是@ConditionalOnMissingBean
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
//傳回一個spec(說明),這裡的spec規定了搜尋的内容,比如搜尋政策、需要搜尋的類名......
spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
matching = this.getMatchingBeans(context, spec);
if (!matching.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, new Object[]{spec}).didNotFind("any beans").atAll();
}
如果第5行傳回的list不是空的,就會傳回ConditionOutcome對象noMatch方法,表示不比對。ConditionOutcome類用于存放過濾結果,隻有兩個變量:
/**
* 過濾結果類
*/
public class ConditionOutcome {
/**
* 比對結果 true or false
*/
private final boolean match;
/**
* 比對結果資訊
*/
private final ConditionMessage message;
兩者差別:
@ConditionOnBean在判斷list的時候,如果list沒有值,傳回false,否則傳回true
@ConditionOnMissingBean在判斷list的時候,如果list沒有值,傳回true,否則傳回false,其他邏輯都一樣
例子:
@ConditionalOnBean(javax.sql.DataSource.class)
Spring容器或者所有父容器中需要存在至少一個javax.sql.DataSource類的執行個體
另外其他幾個擴充:
@ConditionalOnBean(僅僅在目前上下文中存在某個對象時,才會執行個體化一個Bean)
@ConditionalOnClass(某個class位于類路徑上,才會執行個體化一個Bean)
@ConditionalOnExpression(當表達式為true的時候,才會執行個體化一個Bean)
@ConditionalOnMissingBean(僅僅在目前上下文中不存在某個對象時,才會執行個體化一個Bean)
@ConditionalOnMissingClass(某個class類路徑上不存在的時候,才會執行個體化一個Bean)
@ConditionalOnNotWebApplication(不是web應用)
@ConditionalOnProperty 指在application.yml裡配置的屬性是否為true
@Configuration
@ConditionalOnProperty(
value = {"abc.property"},
matchIfMissing = false
)
@ConditionalOnClass(Abc.class)
public class Multi {
@Bean
@ConditionalOnMissingBean({Abc.class})
public String check() {
System.err.println("multi check");
return "check";
}
在配置裡加上abc.property = true這個配置就可以測試上面的代碼了。
參考位址:
https://blog.csdn.net/xcy1193068639/article/details/81517456
https://blog.csdn.net/tianyaleixiaowu/article/details/78201587