以条件装配注解ConditionalOnMissingBean
为例,该注解基于OnBeanCondition
实现
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
// ...
}
查看OnBeanCondition
类继承结构,关注从接口Condition
派生出来的分支
重点关注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); // 匹配结果
logOutcome(classOrMethodName, outcome); // 输出匹配结果,日志级别trace
recordEvaluation(context, classOrMethodName, outcome); // 存储匹配结果
return outcome.isMatch(); // 返回匹配结果,判断某个bean是否应该被Spring容器加载
}
// ...
}
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
}
再来看OnBeanCondition
继承SpringBootCondition
之后,做了什么?
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
// 判断当前Spring容器是否可以加在当前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); // 将匹配结果返回
}
}
接着,回到上面提到的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); // 匹配结果
logOutcome(classOrMethodName, outcome); // 输出匹配结果,日志级别trace
recordEvaluation(context, classOrMethodName, outcome); // 存储匹配结果
return outcome.isMatch(); // 返回匹配结果,判断某个bean是否应该被Spring容器加载
}
// ...
}
/**
* 基于Trace级别,输出匹配结果
*/
protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(getLogMessage(classOrMethodName, outcome));
}
}
/**
* 存储条件装配结果
*/
private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {
if (context.getBeanFactory() != null) {
ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this,
outcome);
}
}
}
上面出现了ConditionEvaluationReport
,这个类将会尝试从Spring容器中获取一个名为autoConfigurationReport的bean,若不存在则创建并注册到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对象
if (beanFactory.containsSingleton(BEAN_NAME)) {
report = beanFactory.getBean(BEAN_NAME, ConditionEvaluationReport.class);
}
// 如果上面判断不存在,则创建一个该类型的对象,并注册到Spring容器中
else {
report = new ConditionEvaluationReport();
beanFactory.registerSingleton(BEAN_NAME, report);
}
locateParent(beanFactory.getParentBeanFactory(), report);
return report;
}
}
/**
* 存储条件装配结果
*/
public void recordConditionEvaluation(String source, Condition condition, ConditionOutcome outcome) {
// 省略入参断言...
this.unconditionalClasses.remove(source);
if (!this.outcomes.containsKey(source)) {
this.outcomes.put(source, new ConditionAndOutcomes()); // 如果当前类还没有建立一个条件匹配结果对象先创建
}
this.outcomes.get(source).add(condition, outcome); // ConditionAndOutcomes是一个迭代对象,添加一个匹配结果
this.addedAncestorOutcomes = false;
}
}
知道了以上的原理,我们就可以获取Spring注册的autoConfigurationReport对象,并从中解析bean的条件装配结果
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类的装配结果为例
conditionAndOutcomesBySource.forEach((key, value) -> {
if (key.toLowerCase().contains("stringredistemplate")) {
System.out.println("类: " + key);
for (ConditionEvaluationReport.ConditionAndOutcome outcome : value) {
System.out.println("条件装配结果 : " + outcome.getOutcome().isMatch() + ",原因: " + outcome.getOutcome().getMessage());
}
}
});
applicationContext.close();
}
}
输出入如下结果:
类:
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration#stringRedisTemplate
条件装配结果 : 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
结合StringRedisTemplate的装配条件,必须有且仅有一个RedisConnectionFactory类型的Bean,必须没有类型为StringRedisTemplate的bean,两个条件都符合,因此这个自动装配的StringRedisTemplate会被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;
}
}