SpringBoot @ConditionalOnBean、@ConditionalOnMissingBean註解原始碼分析與示例
https://blog.csdn.net/xcy1193068639/article/details/81517456
前言:
Spring4推出了@Conditional註解,方便程式根據當前環境或者容器情況來動態注入bean,對@Conditional註解不熟悉的朋友可移步至 Spring @Conditional註解 詳細講解及示例 這篇部落格進行學習。
繼@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類的例項
相關文章
- springboot的@ConditionalOnBean註解Spring BootBean
- SpringBoot原始碼@ConditionalOnBean與@ConditionalOnClass你清楚嗎Spring Boot原始碼Bean
- Springboot中註解@Configuration原始碼分析Spring Boot原始碼
- Nacos(一)原始碼分析Nacos註冊示例流程原始碼
- SpringBoot(16)—@ConditionalOnBean與@ConditionalOnClassSpring BootBean
- SpringBoot原始碼分析之條件註解的底層實現Spring Boot原始碼
- Nacos(二)原始碼分析Nacos服務端註冊示例流程原始碼服務端
- SpringBoot原始碼解讀系列三——引導註解Spring Boot原始碼
- workerman 框架原始碼核心分析和註解框架原始碼
- Flutter與Native通訊示例及原始碼分析Flutter原始碼
- SpringBoot原始碼分析Spring Boot原始碼
- LeakCanary詳解與原始碼分析原始碼
- MapReduce 詳解與原始碼分析原始碼
- SpringBoot原始碼解析-@ConditionalOnXXX註解原理Spring Boot原始碼
- mybatis原始碼-註解sqlMyBatis原始碼SQL
- Java註解與原理分析Java
- nacos註冊中心原始碼流程分析原始碼
- workerman 網路框架原始碼核心分析和註解 over 篇框架原始碼
- SpringBoot2.0原始碼分析(一):SpringBoot簡單分析Spring Boot原始碼
- springcloud/springboot整合NACOS 做註冊和配置中心以及nacos原始碼分析GCCloudSpring Boot原始碼
- Laravel-admin 原始碼分析系列 1——註冊服務與安裝分析Laravel原始碼
- 影片聊天原始碼,同步、非同步示例程式碼分析原始碼非同步
- Spring Ioc原始碼分析系列--@Autowired註解的實現原理Spring原始碼
- @angular/router 原始碼分析之註冊路由Angular原始碼路由
- SpringBoot2.0原始碼分析(二):整合ActiveMQ分析Spring Boot原始碼MQ
- SpringBoot2.0原始碼分析(三):整合RabbitMQ分析Spring Boot原始碼MQ
- Spring 註解學習 詳細程式碼示例Spring
- mybatis中註解對映SQL示例程式碼MyBatisSQL
- SpringBoot註解Spring Boot
- SpringBoot攔截器及原始碼分析Spring Boot原始碼
- springboot 事務建立流程原始碼分析Spring Boot原始碼
- SpringBoot自動裝配-原始碼分析Spring Boot原始碼
- SpringBoot事件監聽器原始碼分析Spring Boot事件原始碼
- 《四 spring原始碼》spring的事務註解@Transactional 原理分析Spring原始碼
- 精盡Spring Boot原始碼分析 - 剖析 @SpringBootApplication 註解Spring Boot原始碼APP
- Spring Boot 基於註解驅動原始碼分析--自動配置Spring Boot原始碼
- SpringBoot原始碼學習4——SpringBoot內嵌Tomcat啟動流程原始碼分析Spring Boot原始碼Tomcat
- 【Java】NIO中Channel的註冊原始碼分析Java原始碼