上一篇分析了springboot的自动配置过程。springboot拿到了需要自动配置的全类名,去加载那些自动配置类。就以springboot自动配置的tomcat举例。会根据不同的条件注解来判断是否加载配置类

那么springboot的条件注解有哪些呢?
条件注解
SpringBoot中的条件注解有:
所有的条件注解都是基于@Conditional来实现的。关于@Conditional属于spring的知识 这里不再赘述。在spring的生命周期中分析过。



所有的条件注解都是通过@Conditional加上对应的条件判断类来实现的。通过几个常用的条件注解类进行分析。
@ConditionalOnBean
@ConditionalOnBean对应的条件判断类是OnBeanCondition。

首先会调用Condition接口的matches方法。Condition是接口,那么就会调到SpringBootCondition的matches方法
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 针对每个条件注解进行条件判断
// 条件注解写在了哪个类上,或哪个方法上
String classOrMethodName = getClassOrMethodName(metadata);
try {
// 条件的判断结果
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 如果log的日志级别为trace,那就直接记录当前条件的判断结果
logOutcome(classOrMethodName, outcome);
// 将判断结果记录到ConditionEvaluationReport中
//ConditionEvaluationReportLoggingListener会在收到ContextRefreshedEvent事件后把判断结果用日志的方式打印出来
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
+ ex.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)", ex);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}
判断的方法在于ConditionOutcome outcome = getMatchOutcome(context, metadata);它是一个抽象方法
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
那么会调到OnBeanCondition的getMatchOutcome方法
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
MergedAnnotations annotations = metadata.getAnnotations();
// 如果存在ConditionalOnBean注解
if (annotations.isPresent(ConditionalOnBean.class)) {
//封装
Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
//底层通过获取bean工厂,从bean工厂获取对应的bean,把结果封装到MatchResult
MatchResult matchResult = getMatchingBeans(context, spec);
// 如果某个Bean不存在
if (!matchResult.isAllMatched()) {
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
// 所有Bean都存在
matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
matchResult.getNamesOfAllMatches());
}
// 如果存在ConditionalOnSingleCandidate注解
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
MatchResult matchResult = getMatchingBeans(context, spec);
// Bean不存在
if (!matchResult.isAllMatched()) {
return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
}
// Bean存在
Set<String> allBeans = matchResult.getNamesOfAllMatches();
// 如果只有一个
if (allBeans.size() == 1) {
matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
}
else {
// 如果有多个
List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,
spec.getStrategy() == SearchStrategy.ALL);
// 没有主Bean,那就不匹配
if (primaryBeans.isEmpty()) {
return ConditionOutcome.noMatch(
spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
}
// 有多个主Bean,那就不匹配
if (primaryBeans.size() > 1) {
return ConditionOutcome
.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
}
// 只有一个主Bean
matchMessage = spec.message(matchMessage)
.found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
.items(Style.QUOTE, allBeans);
}
}
// 存在ConditionalOnMissingBean注解
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
ConditionalOnMissingBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
//有任意一个Bean存在,那就条件不匹配
if (matchResult.isAnyMatched()) {
String reason = createOnMissingBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
// 都不存在在,则匹配
matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
}
上述方法中首先判断如果存在@ConditionalOnBean注解,就会从bean工厂中获取bean,如果能找到,那么就匹配成功,如果找不到就匹配失败,由于@ConditionalOnBean的属性值可能是多个,那么就必须满足所有bean都能找到才算成功。然后不论成功失败,都把结果封装到MatchResult 当中。在判断MatchResult 的结果,如果失败了 就直接返回,如果成功了 接着判断。接着往下判断是否存在@ConditionalOnSingleCandidate直接,如果存在接着判断bean是否存在,是否只有一个bean,等等。判断完这层就会接着判断是否存在@ConditionalOnMissingBean注解。同理判断有没有这个bean。
也就是说OnBeanCondition同时判断了@ConditionalOnBean,@ConditionalOnSingleCandidate,@ConditionalOnMissingBean。


可以看到这两个注解用的也正是OnBeanCondition这个条件类。
条件注解的执行过程就是这样其他注解也同理。
上一篇分析过在从spring.factories获取到所有自动配置类和过滤器filter。首先会通过filter进行过滤,过滤完才会交给springboot加载。接下来就分析如何过滤。
获取到的过滤器filter有

List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
// 逐个利用AutoConfigurationImportFilter来判断所有的自动配置类的条件是否匹配,匹配结果存在match数组中
// 先利用OnBeanCondition进行过滤
// 再利用OnClassCondition进行过滤
// 再利用OnWebApplicationCondition进行过滤
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
// 全部都匹配
if (!skipped) {
return configurations;
}
// 把匹配的记录在result集合中,最后返回
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return result;
}
}
上述代码上一篇有分析,现在来分析filter.match(candidates, this.autoConfigurationMetadata)这部分。还是通过OnBeanCondition举例。由于OnBeanCondition没有实现match方法,调的就是父类FilteringSpringBootCondition的match方法。
@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
// autoConfigurationMetadata就是spring-autoconfigure-metadata.properties中的内容
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
// 首轮不匹配的,进行日志打印,以及记录到ConditionEvaluationReport中去
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}
核心代码就是ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);用来判断是否通过,判断能完成后把结果放到boolean[]数组中返回。而getOutcomes是抽象方法,就会调到子类也就是OnBeanCondition的getOutcomes方法
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
// 遍历处理每个自动配置类
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
// 当前自动配置中@ConditionalOnBean所依赖的类
Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
// 如果onBeanTypes都存在,则返回null
outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
if (outcomes[i] == null) {
// 继续判断@ConditionalOnSingleCandidate所依赖的类
Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
"ConditionalOnSingleCandidate");
outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
}
}
}
return outcomes;
}
到此条件注解的流程就结束了,其他两个条件类流程同理。
对于springboot各种条件注解,首先获取的条件注解中@Conditional的值。调用其抽象父类SpringBootCondition的matches方法。该方法中通过getMatchOutcome(context, metadata)方法将结果封装成ConditionOutcome 。同时getMatchOutcome是抽象方法,具体实现在子类。如果matches方法返回true表示可以加载,返回false表示不需要加载。
在自动配置过程中,从MATE-INF/spring.factories获取到自动配置类和过滤器filter。在过滤的过程中调用了FilteringSpringBootCondition的match方法。该方法中getOutcomes(autoConfigurationClasses, autoConfigurationMetadata)用于判断是否通过,是一个抽象方法在具体子类实现,也就是调用了OnBeanCondition、OnClassCondition、OnWebApplicationCondition的getOutcomes方法。
使用几个常用的条件注解
@ConditionalOnMissingBean和@ConditionalOnBean
@Component
@ConditionalOnMissingBean(DemoBean.class)
public class AaMissBean {
public void test(){
System.out.println("AaMissBean------------test");
}
}
@Component
@ConditionalOnBean(DemoBean.class)
public class AaOnBean {
public void test(){
System.out.println("AaBean------------test");
}
}
@Component
public class DemoBean {
}
测试
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext run = SpringApplication.run(Application.class, args);
Object onBean = run.getBean("aaOnBean");
System.out.println(onBean);
Object missBean = run.getBean("aaMissBean");
System.out.println(missBean);
}
}
由于springboot中存在DemoBean ,那么AaOnBean 就会被加载,而AaMissBean 不会被加载

如果去掉DemoBean 的@Component注解,则相反 那么AaMissBean就会被加载,而AaOnBean 不会被加载

@ConditionalOnProperty
application.yml
sprinboot:
aaaa: jiaza
@ConditionalOnProperty(prefix = “sprinboot”,name = “aaaa”,havingValue = “jiazai”,matchIfMissing = false)
通过配置文件的值来判断是否加载
prefix :前缀
name :名称
havingValue :对应的值
matchIfMissing :默认为false,必须匹配才能加载 如果为true 如果没找到sprinboot.aaaa的值也能加载。
@Component
//配置文件中的springboot.aaaa 必须等于 jiazai 才能加载
@ConditionalOnProperty(prefix = "sprinboot",name = "aaaa",havingValue = "jiazai",matchIfMissing = false)
public class AaProperBean {
public void test(){
System.out.println("----AaProperBean-------");
}
}
测试
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext run = SpringApplication.run(Application.class, args);
AaProperBean aaProperBean = run.getBean("aaProperBean",AaProperBean.class);
aaProperBean.test();
}
}
如果修改配置文件
sprinboot:
aaaa: jiaza222

就不会加载。