以條件裝配注解ConditionalOnMissingBean
為例娃豹,該注解基于OnBeanCondition
實(shí)現(xiàn)
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
// ...
}
查看OnBeanCondition
類繼承結(jié)構(gòu),關(guān)注從接口Condition
派生出來的分支
重點(diǎn)關(guān)注SpringBootCondition
购裙,以下為該類的主要代碼
public abstract class SpringBootCondition implements Condition {
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = getMatchOutcome(context, metadata); // 匹配結(jié)果
logOutcome(classOrMethodName, outcome); // 輸出匹配結(jié)果懂版,日志級(jí)別trace
recordEvaluation(context, classOrMethodName, outcome); // 存儲(chǔ)匹配結(jié)果
return outcome.isMatch(); // 返回匹配結(jié)果,判斷某個(gè)bean是否應(yīng)該被Spring容器加載
}
// ...
}
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
}
再來看OnBeanCondition
繼承SpringBootCondition
之后躏率,做了什么躯畴?
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
// 判斷當(dāng)前Spring容器是否可以加在當(dāng)前Bean
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
MergedAnnotations annotations = metadata.getAnnotations();
if (annotations.isPresent(ConditionalOnBean.class)) {
Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
matchResult.getNamesOfAllMatches());
}
// 省略其他基于@ConditionOnXXXX注解屬性的解析判斷
return ConditionOutcome.match(matchMessage); // 將匹配結(jié)果返回
}
}
接著,回到上面提到的SpringBootCondition
薇芝,看看它獲取了匹配結(jié)果之后蓬抄,做了什么操作?
public abstract class SpringBootCondition implements Condition {
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = getMatchOutcome(context, metadata); // 匹配結(jié)果
logOutcome(classOrMethodName, outcome); // 輸出匹配結(jié)果恩掷,日志級(jí)別trace
recordEvaluation(context, classOrMethodName, outcome); // 存儲(chǔ)匹配結(jié)果
return outcome.isMatch(); // 返回匹配結(jié)果倡鲸,判斷某個(gè)bean是否應(yīng)該被Spring容器加載
}
// ...
}
/**
* 基于Trace級(jí)別,輸出匹配結(jié)果
*/
protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(getLogMessage(classOrMethodName, outcome));
}
}
/**
* 存儲(chǔ)條件裝配結(jié)果
*/
private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {
if (context.getBeanFactory() != null) {
ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this,
outcome);
}
}
}
上面出現(xiàn)了ConditionEvaluationReport
黄娘,這個(gè)類將會(huì)嘗試從Spring容器中獲取一個(gè)名為autoConfigurationReport的bean峭状,若不存在則創(chuàng)建并注冊(cè)到Spring容器中
public final class ConditionEvaluationReport {
private static final String BEAN_NAME = "autoConfigurationReport";
public static ConditionEvaluationReport get(ConfigurableListableBeanFactory beanFactory) {
synchronized (beanFactory) {
ConditionEvaluationReport report;
// 判斷是否存在名為autoConfigurationReport,類型為ConditionEvaluationReport的單例bean對(duì)象
if (beanFactory.containsSingleton(BEAN_NAME)) {
report = beanFactory.getBean(BEAN_NAME, ConditionEvaluationReport.class);
}
// 如果上面判斷不存在逼争,則創(chuàng)建一個(gè)該類型的對(duì)象优床,并注冊(cè)到Spring容器中
else {
report = new ConditionEvaluationReport();
beanFactory.registerSingleton(BEAN_NAME, report);
}
locateParent(beanFactory.getParentBeanFactory(), report);
return report;
}
}
/**
* 存儲(chǔ)條件裝配結(jié)果
*/
public void recordConditionEvaluation(String source, Condition condition, ConditionOutcome outcome) {
// 省略入?yún)嘌?..
this.unconditionalClasses.remove(source);
if (!this.outcomes.containsKey(source)) {
this.outcomes.put(source, new ConditionAndOutcomes()); // 如果當(dāng)前類還沒有建立一個(gè)條件匹配結(jié)果對(duì)象先創(chuàng)建
}
this.outcomes.get(source).add(condition, outcome); // ConditionAndOutcomes是一個(gè)迭代對(duì)象,添加一個(gè)匹配結(jié)果
this.addedAncestorOutcomes = false;
}
}
知道了以上的原理誓焦,我們就可以獲取Spring注冊(cè)的autoConfigurationReport對(duì)象胆敞,并從中解析bean的條件裝配結(jié)果
public class AutoConfigurationReportDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.scan("com.holybell");
applicationContext.refresh();
ConditionEvaluationReport report = applicationContext.getBean("autoConfigurationReport", ConditionEvaluationReport.class);
Map<String, ConditionEvaluationReport.ConditionAndOutcomes> conditionAndOutcomesBySource = report.getConditionAndOutcomesBySource();
// 以獲取StringRedisTemplate類的裝配結(jié)果為例
conditionAndOutcomesBySource.forEach((key, value) -> {
if (key.toLowerCase().contains("stringredistemplate")) {
System.out.println("類: " + key);
for (ConditionEvaluationReport.ConditionAndOutcome outcome : value) {
System.out.println("條件裝配結(jié)果 : " + outcome.getOutcome().isMatch() + ",原因: " + outcome.getOutcome().getMessage());
}
}
});
applicationContext.close();
}
}
輸出入如下結(jié)果:
類:
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration#stringRedisTemplate
條件裝配結(jié)果 : true ,
原因:
@ConditionalOnSingleCandidate (types: org.springframework.data.redis.connection.RedisConnectionFactory; SearchStrategy: all) found a primary bean from beans 'redisConnectionFactory';
@ConditionalOnMissingBean (types: org.springframework.data.redis.core.StringRedisTemplate; SearchStrategy: all) did not find any beans
結(jié)合StringRedisTemplate的裝配條件,必須有且僅有一個(gè)RedisConnectionFactory類型的Bean杂伟,必須沒有類型為StringRedisTemplate的bean移层,兩個(gè)條件都符合,因此這個(gè)自動(dòng)裝配的StringRedisTemplate會(huì)被Spring容器加載
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}