本文是我的学习笔记,请自己思考内容并且实践,有错误的地方欢迎指出。
SpringBoot要求4.3.2.BUILD-SNAPSHOT或以上版本,有一个很重要的原因是因为spring4提供了条件注解@Conditional,而SpringBoot大量地使用了这种注解来为我们自动配置一些信息,可以根据容器是否存在某些bean,jvm版本,指定路径是否存在指定类等等,功能十分强大。
简单的实验@Conditional
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Windows");
}
}
public class LinuxCondition implements Condition{
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux");
}
}
@Bean
@Conditional(WindowsCondition.class)
public WhatIs windows(){
return new Windows();
}
@Bean
@Conditional(LinuxCondition.class)
public WhatIs linux(){
return new Linux();
}
这里的代码其实是JavaEE颠覆者:SpringBoot里面解释条件注解的示例代码。效果是根据系统的名称,这里简单地判断系统名称,然后实例化不同的类。我大概地推测是:@Conditional注解会调用XXXCondition.matches()方法,然后通过返回true or false判定是否接下来生成bean。可以看到LinuxCondition和WindowsCondition都实现了Condition接口,并重写了matches方法。
总结:条件注解的实现步骤:实现org.springframework.context.annotation.Condition接口,并重写matches()方法,生成bean的时候通过@Conditional注解来调用具体的matches()方法判断是否实例化接下来的bean。
基于SpringBoot版本v1.5.8.RELEASE
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class>[] exclude() default {};
String[] excludeName() default {};
}
@EnableAutoConfiguration注解如上,重点在@Import({EnableAutoConfigrationImportSelector.class})
Ctrl+左键
可以进入该方法,可以看到该方法extends AutoConfigurationImportSelector,在这个超类中有一个重要的方法如下:
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
这个方法会扫描具有META-INF/spring.factories文件的jar包,而我们的Spring-boot-autoconfigure-x.x.x.jar就有这个文件。(书上说的)idea快捷键Shift+Ctrl+n
可以快速找到这个文件。
spring.factories文件如下:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
...
这里#Auto Configure注释下面有大量的XXXAutoConfiguration的类,容器在扫描到这些配置类之后会一个一个判断是否符合条件,如何条件则实例化该配置。
我们这里以org.springframework.boot.autoconfigure.aop.AopAutoConfiguration为例。
@Configuration
@ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class})
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"auto"},
havingValue = "true",
matchIfMissing = true
)
public class AopAutoConfiguration {
public AopAutoConfiguration() {
}
@Configuration
@EnableAspectJAutoProxy(
proxyTargetClass = true
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"proxy-target-class"},
havingValue = "true",
matchIfMissing = false
)
public static class CglibAutoProxyConfiguration {
public CglibAutoProxyConfiguration() {
}
}
@Configuration
@EnableAspectJAutoProxy(
proxyTargetClass = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"proxy-target-class"},
havingValue = "false",
matchIfMissing = true
)
public static class JdkDynamicAutoProxyConfiguration {
public JdkDynamicAutoProxyConfiguration() {
}
}
}
这里@Configuration注解首先声明了这是个配置类。
接下来是两个条件,符合这两个条件则实例化该bean。
条件一:@ConditionalOnClass注解是一个组合注解,如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
Class>[] value() default {};
String[] name() default {};
}
这里的条件是:当类路径下有指定类的时候。
条件二:@ConditionalOnProperty也是个组合注解,如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
String[] value() default {};
String prefix() default "";
String[] name() default {};
String havingValue() default "";
boolean matchIfMissing() default false;
boolean relaxedNames() default true;
}
我们注意到,这个条件可以注解在类,接口,方法等,而它的条件是:指定属性存在指定值的时候。
在类内部还有对方法的条件注解也是如此,接下来我们以@ConditionalOnClass为例,看看它如何与我们前置知识的条件注解关联上。
我们直接看类的继承层次:
class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware
public abstract class SpringBootCondition implements Condition
可以看到OnClassCondition继承自SpringBootCondition,SpringBootCondition实现了Condition方法。
SpringBootCondition方法重写了matches()方法如下:
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
this.logOutcome(classOrMethodName, outcome);
this.recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
} catch (NoClassDefFoundError var5) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.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)", var5);
} catch (RuntimeException var6) {
throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
}
}
其中getMatchOutcome是一个模板方法,如下:
public abstract ConditionOutcome getMatchOutcome(ConditionContext var1, AnnotatedTypeMetadata var2);
对模板方法设计模式不陌生的话应该想到了,它的实现是在子类OnClassCondition里,如下:
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
List onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
List onMissingClasses;
if (onClasses != null) {
onMissingClasses = this.getMatches(onClasses, OnClassCondition.MatchType.MISSING, classLoader);
if (!onMissingClasses.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.getMatches(onClasses, OnClassCondition.MatchType.PRESENT, classLoader));
}
onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
List present = this.getMatches(onMissingClasses, OnClassCondition.MatchType.PRESENT, classLoader);
if (!present.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.getMatches(onMissingClasses, OnClassCondition.MatchType.MISSING, classLoader));
}
return ConditionOutcome.match(matchMessage);
}
至此,到这里整个实现过程就理清了。
总结: SprigBoot的@EnableAutoConfiguration通过条件注解@Conditional实现,而为了产生更复杂的行为,SpringBoot用SpringBootCondition实现了Condition,我认为实际上是对Condition的进一步拓展,让它具有更多的作用,之后通过模板方法把判断的权利发放非子类,也就是具体的XXXConfiguration类。
很高兴你能点进来看这篇文章,一篇不是很有深度的文章,如果有任何错误的地方,欢迎指正,并与我交流。
上面关于如何读取spring.factories说得比较含糊,这里有了新的发现,故补充。
提示:内容包含大量本人无验证且目前无能力验证的猜测,请自己理解分析。
@EnableAutoConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class>[] exclude() default {};
String[] excludeName() default {};
}
上面我们提到,@EnableAutoConfiguration中最重要的一个注解是@Import({EnableAutoConfigurationImportSelector.class}),引入spring.factories文件中的xxxAutoConfiguration类的也是这个注解的功劳,因此这里我们主要解析这个注解。
Import的类EnableAutoConfigurationImportSelector.class的继承层次如下:
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered
public interface DeferredImportSelector extends ImportSelector
public interface ImportSelector
其中ImportSelector内部声明了如下方法:
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
这里我认为容器在读取到@Import注解时便会调用import的类的selectImports方法,这是一种学习注解的一种思考方式,当然我不会打包票说就是这样,因为我并没有看过容器是怎么调用它的,这仅仅出于一种猜测。
显然,实现应该是其实现类,那么我们重新往上检索会发现AutoConfigurationImportSelector实现了该方法:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
configurations = this.sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return (String[])configurations.toArray(new String[configurations.size()]);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
这个方法里面有两个比较重要的方法,分别是isEnabled(annotationMetadata)和getCandidateConfigurations(annotationMetadata, attributes)。
isEnabled是一个模板方法,默认返回true
protected boolean isEnabled(AnnotationMetadata metadata) {
return true;
}
实现依然是由其子类EnableAutoConfigurationImportSelector实现:
protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? ((Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true)).booleanValue() : true;
}
而getCandidateConfigurations则通过SpringFactoriesLoader.loadFactoryNames来载入spring.factories这个文件。
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}