SpringBoot在扫描到META-INFO下的spring.factories下的AutoConfiguration类的时候是怎么去判断是否需要注册这个类的呢?这里就有一个很重要的注解@Conditional。在SpringBoot中存在大量的条件注解@ConditionOnXXX,这些注解在自动配置类的上面都能见到,比如:
它们是怎么工作的呢?
先看一下SpringBoot中的常用Condition注解:
条件化注解 | 配置生效条件 |
---|---|
@ConditionalOnBean | 配置了某个特定Bean |
@ConditionalOnMissingBean | 没有配置特定的Bean |
@ConditionalOnClass | Classpath里有指定的类 |
@ConditionalOnMissingClass | Classpath里没有指定的类 |
@ConditionalOnExpression | 给定的SpEL表达式的计算结果为true |
@ConditionalOnJava | Java的版本匹配特定值或者一个范围值 |
@ConditionalOnJndi | 参数中给定的JNDI位置必须存在一个,如果没有给定参数,则要有JNDI InitialContext |
@ConditionalOnProperty | 指定的配置属性要有一个明确的值 |
@ConditionalOnResource | Classpath里有指定的资源 |
@ConditionalOnWebApplication | 是一个Web应用程序 |
@ConditionalOnNotWebApplication | 不是Web应用程序 |
SpringBoot中利用以上注解设置的简单示例:
自定义的自动配置类:MyAutoConfigurationPerson
@Configuration
@EnableConfigurationProperties(MyPersonProperties.class)
@ConditionalOnClass(PersonService.class)
//该属性为true时,配置文件中缺少对应的value或name的对应的属性值,也会注入成功
@ConditionalOnProperty(prefix = "test.person", matchIfMissing = true, name = {"name","address"})
public class MyAutoConfigurationPerson {
private MyPersonProperties myPersonProperties;
public MyAutoConfigurationPerson (MyPersonProperties myPersonProperties) {
this.myPersonProperties = myPersonProperties;
}
@Bean
@ConditionalOnMissingBean(OrderService.class)
public PersonService personService () {
PersonService service = new PersonService();
System.err.println("service在自动配置类被初始化");
System.err.println(myPersonProperties.toString());
return service;
}
}
配置的属性类:
@ConfigurationProperties(prefix = "test.person")
public class MyPersonProperties {
private String name="alen";
private String address="SHANGHAI";
/*省略getter、setter和toString*/
}
待注入的服务类两个
public class PersonService {
public PersonService () {
System.err.println("PersonService被初始化。。。。");
}
}
public class OrderService{
public OrderService () {
System.out.println("OrderService被初始化。。。。");
}
}
application.properties配置:
test.person.address=BEIJING
test.person.name=lucy
测试的话,可以尝试下更改注解属性值、application.properties文件删除给定的配置、OrderService加上@Component注解、@ConditionalOnMissingBean改为@ConditionalOnBean等查看类加载情况。
简单的自定义一个@Conditional注解,看他是如何工作的:
自定义的注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Conditional(TestConditional.class)
public @interface ConditionOnTest {
String environment () default "dev";
}
匹配用的Condition接口实现类
public class TestConditional implements Condition {
@Override
public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionOnTest.class.getName());
String environment =(String) attributes.get("environment");
//如果environment.equals("test")返回true,则表示注册该类
return environment.equals("test");
}
}
测试类
@Component
@ConditionOnTest(environment = "test")
public class PersonService {
public PersonService () {
System.err.println("PersonService被初始化。。。。");
}
}
测试类上的注解值如果environment = “test”,将会在控制台“打印PersonService被初始化。。。。”。
如果将此值修改,则在启动时将不会看到构造函数输出被打印。
以自定义的@Conditional注解Demo为例查看@Conditional注解的作用逻辑。
此类起作用肯定是TestConditional类在其中起了作用,进入debug模式,在matchs方法打上断点,简单查看下它的工作流程:
1.从Debug可以看出,是从解析配置类的包扫描,扫描到该组件。
然后再去判断该组件是否需要跳过注册,可以看到最终调用到了org.springframework.context.annotation.ConditionEvaluator#shouldSkip(org.springframework.core.type.AnnotatedTypeMetadata)方法
大致逻辑为先判断是否存在@Conditional注解,如果存在,则获取conditionClasses 并实例化Condition 实现类在调用matchs方法进行判断。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
//判断元数据是否存在Conditional注解
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
省略.....
//获取Condition类,demo此处为TestConditional类
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
//排序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
//此处调用了matchs方法判断
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
如果返回false,后续将不会放入候选的集合中,则Spring将不会注册该类。
第一个Demo中的配置也是如此,可以看下@ConditionalOnClass(PersonService.class)注解,此注解底层依旧是@Conditional注解
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
所以可以直接在OnClassCondition类的getOutcome方法上打上断点去查看。
SpringBoot的大致处理逻辑也是一样,简单看一下SpringBoot启动时去怎么选择哪些自动配置项的。
1.先看一下Condition接口在SpringBoot中的应用,Condition接口在SpringBoot中的基础类为SpringBootCondition类,在其上注释为:
/**
* Base of all {@link Condition} implementations used with Spring Boot. Provides sensible
* logging to help the user diagnose what classes are loaded.
* */
public abstract class SpringBootCondition implements Condition {
/*。。。。*/
}
SpringBootCondition有很多子类,此处只展示本处演示所在意的类的类图,其他再说。。。(此处只关注AutoConfigurationImportFilter类,因为下面会用到)
来简单看下SpringCondition类:
在此类的matchs方法中并没有太多逻辑,只是一个模板方法有子类去实现.
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
/*省略。。。。*/
//此处为abstract方法,由子类去实现各自的逻辑(模板方法)
ConditionOutcome outcome = getMatchOutcome(context, metadata);
//日志
logOutcome(classOrMethodName, outcome);
//记录
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
/*省略。。。。*/
FilteringSpringBootCondition类中也并没有实现getMatchOutcome()方法,那现在去OnWebApplicationCondition中具体看一下怎么实现的:
/**
* 根据元数据的配置来过滤自动配置类
* @Param autoConfigurationClasses 候选自动配置类集合
* @Param autoConfigurationMetadata 候选自动配置类元数据
* @Return outcomes 匹配完的结果集
*/
@Override
protected 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) {
//具体判断逻辑
//autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"),
//以this.properties.getProperty(className + "." + key)获取配置的参数,如果为空则取值null
outcomes[i] = getOutcome(
autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));
}
}
return outcomes;
}
具体的判断逻辑,如果没有匹配的属性值,则直接返回空,否则再去与属性值进行比较,进行判断。
private ConditionOutcome getOutcome(String type) {
if (type == null) {
return null;
}
ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class);
//判断@ConditionalOnWebApplication上的type属性是否与源信息的type相等
if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {
//判断是否存在该类,如果不存在则不匹配
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
}
}
/*省略。。。。*/
return null;
}
在第一篇中我们看到处理这些候选的自动配置类是在
org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports
————————》
org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports
————————》
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry()此方法处,具体在:
该行代码处configurations = getConfigurationClassFilter().filter(configurations);
该行代码分两部分查看:
private ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
//获取filters,此处进去我们可以看到其实例化了AutoConfigurationImportFilter类的三个子类
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
//执行Aware方法,注入属性
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
//包装到ConfigurationClassFilter,并扫描到metadata赋值给autoConfigurationMetadata
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}
configurations由之前可知此处已经是所有相关Jar包中spring.factories中经过过滤和去重的配置信息
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
//候选的自动配置类
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
//三个过滤器进行过滤
for (AutoConfigurationImportFilter filter : this.filters) {
//调用各自的match方法---》在父类SpringBootApplication中实现----》实际调用方法为getOutcomes,由各子类实现
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
//判断该位置的元素是否通过匹配,不匹配设置为null,跳过设置为true,不直接返回候选结果
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);
}
}
/*省略。。。。*/
return result;
}
可以看到匹配的未找到的情况:
此处最终返回匹配完成的结果并做一些处理,最终调用到:
org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports方法处进行注册:
getImports()逻辑执行完毕,获取的集合为Iterable
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
//grouping.getImports()为返回的需要导入的自动配置类的集合
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
//此处直接调用processImports注册该类
//this.importStack.registerImport(
// currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
到此,SpringBoot自动配置类注册完成。