前言:
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類的實例