spring自动装配
以前我们使用spring引入一个中间件的客户端,都需要在maven中引入jar包后,再通过代码或者xml配置文件的方式实例化我们所需要的bean。通常我们需要学习如何配置,学习实例化哪些bean,才能让功能正常使用。而通过自动装配的方式,我们只需要引入对应jar包,jar包中的bean会自动被扫描并实例化,用户很简单的就可以开始使用新引入的功能。
自动装配的原理
使用自动装配的第一步,就是在启动类上引入注解@EnableAutoConfiguration
@SuppressWarnings("deprecation")
@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 {};
}
可以看到该注解引入了EnableAutoConfigurationImportSelector.class,它继承了AutoConfigurationImportSelector.class,而AutoConfigurationImportSelector的功能是将所有自动化配置加载到容器。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
//读取classLoader下所有资源的 "META-INF/spring-autoconfigure-metadata.properties"文件,把所有值封装为Properties
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//读取注解@EnableAutoConfiguration的参数
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//读取classLoader下所有资源的 "META-INF/spring.factories"文件,获取所有要自动装配的类路径
List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);
//去重
configurations = removeDuplicates(configurations);
//排序,spring-autoconfigure-metadata.properties中有定义加载顺序
configurations = sort(configurations, autoConfigurationMetadata);
//根据注解的参数,获取不可用的配置,还有配置文件中通过spring.autoconfigure.exclude定义的类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//检查哪些排除的类不可用
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//通过配置的AutoConfigurationImportFilter过滤
configurations = filter(configurations, autoConfigurationMetadata);
//通过配置的监听器监听自动配置监听事件<OnAutoConfigurationImportEvent>
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
我们一步一步看一下过程,首先是AutoConfigurationMetadataLoader如何读取资源。可以看到该过程比较简单,利用jdk的ClassLoader读取资源。
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils
.loadProperties(new UrlResource(urls.nextElement())));
}
return loadMetadata(properties);
}
catch (IOException ex) {
...
}
}
getCandidateConfigurations方法获取自动配置的类,也是利用classLoader获取
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), 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;
}
我们先看一下spring.factories和spring-autoconfigure-metadata.properties的格式。spring.factories指定了,哪些类会被自动加载;spring-autoconfigure-metadata.properties定了自动配置类被加载的条件和顺序,当然自动配置类本身也定义了条件和顺序
##spring-autoconfigure-metadata.properties
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration.ConditionalOnClass=com.netflix.discovery.EurekaClientConfig
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration.Configuration=
##spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration
接下来看一下排序逻辑,AutoConfigurationSorter负责实际的排序。
class AutoConfigurationSorter {
private final MetadataReaderFactory metadataReaderFactory;
private final AutoConfigurationMetadata autoConfigurationMetadata;
public List<String> getInPriorityOrder(Collection<String> classNames) {
//AutoConfigurationClass会读取每一个自动配置类的注解,记录下前序类和后序类,还有执行顺序。如果spring-autoconfigure-metadata.properties中定义了顺序则该顺序优先。
final AutoConfigurationClasses classes = new AutoConfigurationClasses(
this.metadataReaderFactory, this.autoConfigurationMetadata, classNames);
List<String> orderedClassNames = new ArrayList<String>(classNames);
// Initially sort alphabetically
Collections.sort(orderedClassNames);
// Then sort by order
Collections.sort(orderedClassNames, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int i1 = classes.get(o1).getOrder();
int i2 = classes.get(o2).getOrder();
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
});
//通过前面读取出的前序类和后序类,进行排序,里面包含一个递归算法
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
return orderedClassNames;
}
}
看一下过滤的流程
private List<String> filter(List<String> configurations,AutoConfigurationMetadata autoConfigurationMetadata) {
String[] candidates = configurations.toArray(new String[configurations.size()]);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
//getAutoConfigurationImportFilters会从spring.factories中读取定义好的AutoConfigurationImportFilter类
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
//如果AutoConfigurationImportFilter实现了BeanClassLoaderAware、BeanFactoryAware等类,会把对应的类写到filter里
invokeAwareMethods(filter);
//过滤
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
skip[i] = true;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<String>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
return new ArrayList<String>(result);
}
监听器的执行和过滤的流程类似,全部执行完之后,需要自动注入的类名单就交给容器去处理了。之后需要了解bean的加载,自动配置和@configuration的顺序,如何才能控制哪些类不加载