springboot各种条件注解解析
上一篇分析了springboot的自动配置过程。springboot拿到了需要自动配置的全类名,去加载那些自动配置类。就以springboot自动配置的tomcat举例。会根据不同的条件注解来判断是否加载配置类
那么springboot的条件注解有哪些呢?
条件注解
SpringBoot中的条件注解有:
- ConditionalOnBean:是否存在某个某类或某个名字的Bean
- ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
- ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
- ConditionalOnClass:是否存在某个类
- ConditionalOnMissingClass:是否缺失某个类
- ConditionalOnExpression:指定的表达式返回的是true还是false
- ConditionalOnJava:判断Java版本
- ConditionalOnJndi:JNDI指定的资源是否存在
- ConditionalOnWebApplication:当前应用是一个Web应用
- ConditionalOnNotWebApplication:当前应用不是一个Web应用
- ConditionalOnProperty:Environment中是否存在某个属性
- ConditionalOnResource:指定的资源是否存在
- ConditionalOnWarDeployment:当前项目是不是以War包部署的方式运行
- ConditionalOnCloudPlatform:是不是在某个云平台上
所有的条件注解都是基于@Conditional来实现的。关于@Conditional属于spring的知识 这里不再赘述。在spring的生命周期中分析过。
所有的条件注解都是通过@Conditional加上对应的条件判断类来实现的。通过几个常用的条件注解类进行分析。
@ConditionalOnBean
@ConditionalOnBean对应的条件判断类是OnBeanCondition。
首先会调用Condition接口的matches方法。Condition是接口,那么就会调到SpringBootCondition的matches方法
@Overridepublic 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方法
@Overridepublic 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));}// 只有一个主BeanmatchMessage = 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
方法。
@Overridepublic 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方法
@Overrideprotected 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都存在,则返回nulloutcomes[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
就不会加载。