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方法
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
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();
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());
}
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
}
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);
if (primaryBeans.isEmpty()) {
return ConditionOutcome.noMatch(
spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
}
if (primaryBeans.size() > 1) {
return ConditionOutcome
.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
}
matchMessage = spec.message(matchMessage)
.found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
.items(Style.QUOTE, allBeans);
}
}
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
ConditionalOnMissingBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
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;
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;
}
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);
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());
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) {
Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
if (outcomes[i] == null) {
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
@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
就不会加载。
|