@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源代码得知@Import(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。@Import(AutoConfigurationImportSele-ctor.class)又可以分为两部分:@Import和对应的ImportSelector。本节重点讲解@Import的基本使用方法和ImportSelector的实现类AutoConfigurationImportSelector。
@Import注解位于spring-context项目内,主要提供导入配置类的功能。为什么要专门讲解@Import的功能及使用呢?如果查看Spring Boot的源代码,我们会发现大量的EnableXXX类都使用了该注解。了解@Import注解的基本使用方法,能够帮助我们更好地进行源代码的阅读和理解。
@Import的源码如下。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { Class<?>[] value(); }
@Import的作用和xml配置中<import/>标签的作用一样,我们可以通过@Import引入@Configuration注解的类,也可以导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类,还可以通过@Import导入普通的POJO(将其注册成Spring Bean,导入POJO需要Spring 4.2以上版本)。
关于@Import导入@Configuration注解类和POJO的功能比较简单和常见,就不再展开介绍了。下面重点介绍ImportSelector接口的作用。
@Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引入哪些@Configuration。ImportSelector接口源码如下。
public interface ImportSelector { String[] selectImports(AnnotationMetadata importingClassMetadata); }
ImportSelector接口只提供了一个参数为AnnotationMetadata的方法,返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectImports方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符串数组的形式返回。
如果实现了接口ImportSelector的类的同时又实现了以下4个Aware接口,那么Spring保证在调用ImportSelector之前会先调用Aware接口的方法。这4个接口为:EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware和ResourceLoaderAware。
在AutoConfigurationImportSelector的源代码中就实现了这4个接口,部分源代码及Aware的全限定名代码如下。
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.DeferredImportSelector; import org.springframework.core.Ordered; public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { ... }
在上面的源代码中,AutoConfigurationImportSelector并没有直接实现ImportSelector接口,而是实现了它的子接口DeferredImportSelector。DeferredImportSelector接口与ImportSelector的区别是,前者会在所有的@Configuration类加载完成之后再加载返回的配置类,而ImportSelector是在加载完@Configuration类之前先去加载返回的配置类。
DeferredImportSelector的加载顺序可以通过@Order注解或实现Ordered接口来指定。同时,DeferredImportSelector提供了新的方法getImportGroup()来跨DeferredImportSelector实现自定义Configuration的加载顺序。
下面我们通过图2-3所示的流程图来从整体上了解AutoConfigurationImportSelector的核心功能及流程,然后再对照代码看具体的功能实现。图2-3中省略了外部通过@Import注解调用该类的部分。
图2-3 AutoConfigurationImportSelector核心功能及流程图
当AutoConfigurationImportSelector被@Import注解引入之后,它的selectImports方法会被调用并执行其实现的自动装配逻辑。读者朋友需注意,selectImports方法几乎涵盖了组件自动装配的所有处理逻辑。AutoConfigurationImportSelector的selectImports方法源代码如下:
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 检查自动配置功能是否开启,默认为开启 if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } // 加载自动配置的元信息,配置文件为类路径中META-INF目录下的 // spring-autoconfigure-metadata.properties文件 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); // 封装将被引入的自动配置信息 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry( autoConfigurationMetadata, annotationMetadata); // 返回符合条件的配置类的全限定名数组 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); // 通过SpringFactoriesLoader类提供的方法加载类路径中META-INF目录下的 // spring.factories文件中针对EnableAutoConfiguration的注册配置类 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 对获得的注册配置类集合进行去重处理,防止多个项目引入同样的配置类 configurations = removeDuplicates(configurations); // 获得注解中被exclude或excludeName所排除的类的集合 Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 检查被排除类是否可实例化,是否被自动注册配置所使用,不符合条件则抛出异常 checkExcludedClasses(configurations, exclusions); // 从自动配置类集合中去除被排除的类 configurations.removeAll(exclusions); // 检查配置类的注解是否符合spring.factories文件中AutoConfigurationImportFilter指定的注解检查条件 configurations = filter(configurations, autoConfigurationMetadata); // 将筛选完成的配置类和排查的配置类构建为事件类,并传入监听器。监听器的配置在于spring.factories文件中,通过AutoConfigurationImportListener指定 fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
通过图2-3和上述代码,我们从整体层面了解了AutoConfigurationImportSelector的概况及操作流程,后文将对这些流程进行细化拆分,并通过阅读源代码来分析Spring Boot是如何实现自动加载功能的。
检查自动配置是否开启的代码位于AutoConfigurationImportSelector的selectImports方法第一段中。如果开启自动配置功能,就继续执行后续操作;如果未开启,就返回空数组。代码如下。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } ... }
该方法主要使用isEnabled方法来判断自动配置是否开启,代码如下。
protected boolean isEnabled(AnnotationMetadata metadata) { if (getClass() == AutoConfigurationImportSelector.class) { return getEnvironment().getProperty( EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true); } return true; }
通过isEnabled方法可以看出,如果当前类为AutoConfigurationImportSelector,程序会从环境中获取key为EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY的配置,该常量的值为spring.boot.enableautoconfiguration。如果获取不到该属性的配置,isEnabled默认为true,也就是默认会使用自动配置。如果当前类为其他类,直接返回true。
如果想覆盖或重置EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY的配置,可获取该常量的值,并在application.properties或application.yml中针对此参数进行配置。以application.properties配置关闭自动配置为例,代码如下。
spring.boot.enableautoconfiguration=false
加载元数据配置主要是为后续操作提供数据支持。我们先来看加载相关源代码的具体实现,该功能的代码依旧在selectImports方法内。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { ... AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); ... }
加载元数据的配置用到了AutoConfigurationMetadataLoader类提供的loadMetaData方法,该方法会默认加载类路径下META-INF/spring-autoconfigure-metadata.properties内的配置。
final class AutoConfigurationMetadataLoader { // 默认加载元数据的路径 protected static final String PATH = "META-INF/spring-autoconfigure-metadata. properties"; // 默认调用改方法,传入默认PATH static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) { return loadMetadata(classLoader, PATH); } static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) { try { // 获取数据存储于Enumeration中 Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources (path) : ClassLoader.getSystemResources(path); Properties properties = new Properties(); while (urls.hasMoreElements()) { // 遍历Enumeration中的URL,加载其中的属性,存储到Properties中 properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement()))); } return loadMetadata(properties); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex); } } // 创建AutoConfigurationMetadata的实现类PropertiesAutoConfigurationMetadata static AutoConfigurationMetadata loadMetadata(Properties properties) { return new PropertiesAutoConfigurationMetadata(properties); } // AutoConfigurationMetadata的内部实现类 private static class PropertiesAutoConfigurationMetadata implements AutoCon- figurationMetadata { ... } ... }
在上面的代码中,AutoConfigurationMetadataLoader调用loadMetadata(ClassLoader classLoader)方法,会获取默认变量PATH指定的文件,然后加载并存储于Enumeration数据结构中。随后,从变量PATH指定的文件中获取其中配置的属性存储于Properties内,最终调用在该类内部实现的AutoConfigurationMetadata的子类的构造方法。
spring-autoconfigure-metadata.properties文件内的配置格式如下。
自动配置类的全限定名.注解名称=值
如果spring-autoconfigure-metadata.properties文件内有多个值,就用英文逗号分隔,例如:
... org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration.ConditionalOnClass=org.springframework.data.jdbc.repository.config.JdbcConfiguration,org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations ...
为什么要加载此元数据呢?加载元数据主要是为了后续过滤自动配置使用。Spring Boot使用一个Annotation的处理器来收集自动加载的条件,这些条件可以在元数据文件中进行配置。Spring Boot会将收集好的@Configuration进行一次过滤,进而剔除不满足条件的配置类。
在官方文档中已经明确指出,使用这种配置方式可以有效缩短Spring Boot的启动时间,减少@Configuration类的数量,从而减少初始化Bean的耗时。后续章节中我们会看到过滤自动配置的具体使用方法。
加载自动配置组件是自动配置的核心组件之一,这些自动配置组件在类路径中META-INF目录下的spring.factories文件中进行注册。Spring Boot预置了一部分常用组件,如果我们需要创建自己的组件,可参考Spring Boot预置组件在自己的Starters中进行配置,在后面的章节中会专门对此进行讲解。
通过Spring Core提供的SpringFactoriesLoader类可以读取spring.factories文件中注册的类。下面我们通过源代码来看一下如何在AutoConfigurationImportSelector类中通过getCandidateConfigurations方法来读取spring.factories文件中注册的类。
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; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
getCandidateConfigurations方法使用SpringFactoriesLoader类提供的loadFactoryNames方法来读取META-INF/spring.factories中的配置。如果程序未读取到任何配置内容,会抛出异常信息。而loadFactoryNames方法的第一个参数为getSpringFactoriesLoaderFactoryClass方法返回的EnableAutoConfiguration.class,也就是说loadFactoryNames只会读取配置文件中针对自动配置的注册类。
SpringFactoriesLoader类的loadFactoryNames方法相关代码如下。
public final class SpringFactoriesLoader { // 概类加载文件的路径,可能存在多个 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; ... // 加载所有的META-INF/spring.factories文件,封装成Map,并从中获取指定类名的列表 public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } // 加载所有的META-INF/spring.factories文件,封装成Map,Key为接口的全类名,Value为对应配置值的List集合 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName : StringUtils.commaDelimitedListTo- StringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } ... }
简单描述以上加载的过程就是:SpringFactoriesLoader加载器加载指定ClassLoader下面的所有META-INF/spring.factories文件,并将文件解析内容存于Map<String,List<String>>内。然后,通过loadFactoryNames传递过来的class的名称从Map中获得该类的配置列表。
结合下面spring.factories文件的内容格式,我们可以更加清晰地了解Map<String,List<String>>中都存储了什么。
... # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfig- uration,\ 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,\ ...
以上代码仅以EnableAutoConfiguration配置的部分内容为例,spring.factories文件的基本格式为自动配置类的全限定名=值,与2.3.5节中介绍的元数据的格式很相似,只不过缺少了“.注解名称”部分,如果包含多个值,用英文逗号分隔。
我们继续以EnableAutoConfiguration的配置为例,Map<String,List<String>>内存储的对应数据就是key值为org.springframework.boot.autoconfigure.EnableAutoConfiguration,Value值为其等号后面以分号分割的各种AutoConfiguration类。
当然,spring.factories文件内还有其他的配置,比如用于监听的Listeners和用于过滤的Filters等。很显然,在加载自动配置组件时,此方法只用到了EnableAutoConfiguration对应的配置。
因为程序默认加载的是ClassLoader下面的所有META-INF/spring.factories文件中的配置,所以难免在不同的jar包中出现重复的配置。我们可以在源代码中使用Set集合数据不可重复的特性进行去重操作。
protected final <T> List<T> removeDuplicates(List<T> list) { return new ArrayList<>(new LinkedHashSet<>(list)); }
在2.3.6节中我们获得了spring.factories文件中注册的自动加载组件,但如果在实际应用的过程中并不需要其中的某个或某些组件,可通过配置@EnableAutoConfiguration的注解属性exclude或excludeName进行有针对性的排除,当然也可以通过配置文件进行排除。先通过源代码看看如何获取排除组件的功能。
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 创建Set集合并把待排除的内容存于集合内,LinkedHashSet具有不可重复性 Set<String> excluded = new LinkedHashSet<>(); excluded.addAll(asList(attributes, "exclude")); excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName"))); excluded.addAll(getExcludeAutoConfigurationsProperty()); return excluded; } private List<String> getExcludeAutoConfigurationsProperty() { if (getEnvironment() instanceof ConfigurableEnvironment) { Binder binder = Binder.get(getEnvironment()); return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class) .map(Arrays::asList).orElse(Collections.emptyList()); } String[] excludes = getEnvironment() .getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class); return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList(); }
AutoConfigurationImportSelector中通过调用getExclusions方法来获取被排除类的集合。它会收集@EnableAutoConfiguration注解中配置的exclude属性值、excludeName属性值,并通过方法getExcludeAutoConfigurationsProperty获取在配置文件中key为spring.autoconfigure.exclude的配置值。以排除自动配置DataSourceAutoConfiguration为例,配置文件中的配置形式如下。
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSource- AutoConfiguration
获取到被排除组件的集合之后,首先是对待排除类进行检查操作,代码如下。
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) { List<String> invalidExcludes = new ArrayList<>(exclusions.size()); // 遍历并判断是否存在对应的配置类 for (String exclusion : exclusions) { if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) { invalidExcludes.add(exclusion); } } // 如果不为空,就进行处理 if (!invalidExcludes.isEmpty()) { handleInvalidExcludes(invalidExcludes); } } // 抛出指定异常 protected void handleInvalidExcludes(List<String> invalidExcludes) { StringBuilder message = new StringBuilder(); for (String exclude : invalidExcludes) { message.append("\t- ").append(exclude).append(String.format("%n")); } throw new IllegalStateException(String .format("The following classes could not be excluded because they are" + " not auto-configuration classes:%n%s", message)); }
以上代码中,checkExcludedClasses方法用来确保被排除的类存在于当前的ClassLoader中,并且包含在spring.factories注册的集合中。如果不满足以上条件,调用handleInvalidExcludes方法抛出异常。
如果被排除类都符合条件,调用configurations.removeAll(exclusions)方法从自动配置集合中移除被排除集合的类,至此完成初步的自动配置组件排除。
当完成初步的自动配置组件排除工作之后,AutoConfigurationImportSelector会结合在此之前获取的AutoConfigurationMetadata对象,对组件进行再次过滤。
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); String[] candidates = StringUtils.toStringArray(configurations); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilte- rs()) { invokeAwareMethods(filter); boolean[] match = filter.match(candidates, autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; candidates[i] = null; skipped = true; } } } if (!skipped) { return configurations; } List<String> result = new ArrayList<>(candidates.length); for (int i = 0; i < candidates.length; i++) { if (!skip[i]) { result.add(candidates[i]); } } ... return new ArrayList<>(result); } protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); }
下面,我们先来明确一下都有哪些数据参与了以上两种方法,然后再进行业务逻辑的梳理。
·configurations:List,经过初次过滤之后的自动配置组件列表。
·autoConfigurationMetadata:AutoConfigurationMetadata,元数据文件META-INF/spring-autoconfigure-metadata.properties中配置对应实体类。
·List:META-INF/spring.factories中配置key为Auto-ConfigurationImportFilter的Filters列表。
getAutoConfigurationImportFilters方法是通过SpringFactoriesLoader的loadFactories方法将META-INF/spring.factories中配置key为AutoConfigurationImportFilter的值进行加载。下面为META-INF/spring.factories中相关配置的具体内容。
# Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
在spring-boot-autoconfigure中默认配置了3个筛选条件,OnBeanCondition、OnClassCondition和OnWebApplicationCondition,它们均实现了AutoConfigurationImportFilter接口。
在明确了以上信息之后,该filter方法的过滤功能就很简单了。用一句话来概述就是:对自动配置组件列表进行再次过滤,过滤条件为该列表中自动配置类的注解得包含在OnBeanCondition、OnClassCondition和OnWebApplicationCondition中指定的注解,依次包含@ConditionalOnBean、@ConditionalOnClass和@ConditionalOnWebApplication。
那么在这个实现过程中,AutoConfigurationMetadata对应的元数据和AutoConfiguration-ImportFilter接口及其实现类是如何进行具体筛选的呢?我们先来看一下AutoConfiguration-ImportFilter接口相关类的结构及功能,如图2-4所示。
图2-4 AutoConfigurationImportFilter接口相关类的结构及功能实现图
下面进行相关的源代码及步骤的分解。我们已经知道AutoConfigurationImportFilter接口可以在spring.factories中注册过滤器,用来过滤自动配置类,在实例化之前快速排除不需要的自动配置,代码如下。
@FunctionalInterface public interface AutoConfigurationImportFilter { boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata); }
match方法接收两个参数,一个是待过滤的自动配置类数组,另一个是自动配置的元数据信息。match返回的结果为匹配过滤后的结果布尔数组,数组的大小与String[ ] autoConfigurationClasses一致,如果需排除,设置对应值为false。
图2-4中已经显示AutoConfigurationImportFilter接口的match方法主要在其抽象子类中实现,而抽象子类FilteringSpringBootCondition在实现match方法的同时又定义了新的抽象方法getOutcomes,继承该抽象类的其他3个子类均实现了getOutcomes方法,代码如下。
abstract class FilteringSpringBootCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoader Aware { ... @Override public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { ... 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()); ... } return match; } // 过滤核心功能,该方法由子类实现 protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigur ationClasses, AutoConfigurationMetadata autoConfigurationMetadata); ... }
通过上面的源码可以看出,match方法在抽象类FilteringSpringBootCondition中主要的功能就是调用getOutcomes方法,并将其返回的结果转换成布尔数组。而相关的过滤核心功能由子类实现的getOutcomes方法来实现。
下面以实现类OnClassCondition来具体说明执行过程。首先看一下入口方法getOutcomes的源代码。
@Order(Ordered.HIGHEST_PRECEDENCE) class OnClassCondition extends FilteringSpringBootCondition { @Override protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { // 如果有多个处理器,采用后台线程处理 if (Runtime.getRuntime().availableProcessors() > 1) { return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigu- rationMetadata); } else { OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0, autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader()); return outcomesResolver.resolveOutcomes(); } } ... }
Spring Boot当前版本对getOutcomes方法进行了性能优化,根据处理器的情况不同采用了不同的方式进行操作。如果使用多个处理器,采用后台线程处理(之前版本的实现方法)。否则,getOutcomes直接创建StandardOutcomesResolver来处理。
在resolveOutcomesThreaded方法中主要采用了分半处理的方法来提升处理效率,而核心功能都是在内部类StandardOutcomesResolver的resolveOutcomes方法中实现。
resolveOutcomesThreaded的分半处理实现代码如下。
@Order(Ordered.HIGHEST_PRECEDENCE) class OnClassCondition extends FilteringSpringBootCondition { private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { int split = autoConfigurationClasses.length / 2; OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigu- rationClasses, 0, split, autoConfigurationMetadata); OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(au- toConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, getBean- ClassLoader()); ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes(); ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes(); ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClas- ses.length]; System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length); System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length); return outcomes; } }
内部类StandardOutcomesResolver的源代码重点关注getOutcomes方法的实现,它实现了获取元数据中的指定配置,间接调用getOutcome(String className,ClassLoader classLoader)方法来判断该类是否符合条件,部分源代码如下。
@Order(Ordered.HIGHEST_PRECEDENCE) class OnClassCondition extends FilteringSpringBootCondition { ... private final class StandardOutcomesResolver implements OutcomesResolver { ... private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfiguration- Metadata) { ConditionOutcome[] outcomes = new ConditionOutcome[end - start]; for (int i = start; i < end; i++) { String autoConfigurationClass = autoConfigurationClasses[i]; if (autoConfigurationClass != null) { String candidates = autoConfigurationMetadata .get(autoConfigurationClass, "ConditionalOnClass"); if (candidates != null) { outcomes[i - start] = getOutcome(candidates); } } } return outcomes; } ... // 判断该类是否符合条件 private ConditionOutcome getOutcome(String className, ClassLoader class- Loader) { if (ClassNameFilter.MISSING.matches(className, classLoader)) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnClass.class) .didNotFind("required class").items(Style.QUOTE, className)); } return null; } } }
在获取元数据指定配置的功能时用到了AutoConfigurationMetadata接口的get(String className,String key)方法,而该方法由类AutoConfigurationMetadataLoader来实现。该类在前面的章节已经提过了,它会加载META-INF/spring-autoconfigure-metadata.properties中的配置。
final class AutoConfigurationMetadataLoader { protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; ... private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata { ... @Override public String get(String className, String key) { return get(className, key, null); } @Override public String get(String className, String key, String defaultValue) { String value = this.properties.getProperty(className + "." + key); return (value != null) ? value : defaultValue; } } }
AutoConfigurationMetadataLoader的内部类PropertiesAutoConfigurationMetadata实现了AutoConfigurationMetadata接口的具体方法,其中包含我们用到的get(String className,String key)方法。
根据get方法实现过程,我们不难发现,在getOutcomes方法中获取到的candidates,其实就是META-INF/spring-autoconfigure-metadata.properties文件中配置的key为自动加载注解类+“.”+“ConditionalOnClass”的字符串,而value为其获得的值。以数据源的自动配置为例,寻找到的对应元数据配置如下。
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.datasource.embedded. EmbeddedDatabaseType
key为自动加载组件org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,加上“.”,再加上当前过滤条件中指定的ConditionalOnClass。然后,根据此key获得的value值为javax.sql.DataSource,org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType。
当获取到对应的candidates值之后,最终会调用getOutcome(String className,ClassLoader classLoader)方法,并在其中使用枚举类ClassNameFilter.MISSING的matches方法来判断candidates值是否匹配。而枚举类ClassNameFilter位于OnClassCondition继承的抽象类FilteringSpringBootCondition中。
abstract class FilteringSpringBootCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware { ... protected enum ClassNameFilter { PRESENT { @Override public boolean matches(String className, ClassLoader classLoader) { return isPresent(className, classLoader); } }, MISSING { @Override public boolean matches(String className, ClassLoader classLoader) { return !isPresent(className, classLoader); } }; public abstract boolean matches(String className, ClassLoader classLoader); // 通过类加载是否抛出异常来判断该类是否存在 public static boolean isPresent(String className, ClassLoader classLoader) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } try { forName(className, classLoader); return true; } catch (Throwable ex) { return false; } } // 进行类加载操作 private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException { if (classLoader != null) { return classLoader.loadClass(className); } return Class.forName(className); } } }
ClassNameFilter的匹配原则很简单,就是通过类加载器去加载指定的类。如果指定的类加载成功,即没有抛出异常,说明ClassNameFilter匹配成功。如果抛出异常,说明ClassNameFilter匹配失败。
至此,整个过滤过程的核心部分已经完成了。我们再用一张简单的流程图来回顾整个过滤的过程,如图2-5所示。
图2-5 AutoConfigurationImportFilter接口相关功能及实现类流程图
在完成了以上步骤的过滤、筛选之后,我们最终获得了要进行自动配置的类的集合,在将该集合返回之前,在AutoConfigurationImportSelector类中完成的最后一步操作就是相关事件的封装和广播,相关代码如下。
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) { List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); if (!listeners.isEmpty()) { AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions); for (AutoConfigurationImportListener listener : listeners) { invokeAwareMethods(listener); listener.onAutoConfigurationImportEvent(event); } } } protected List<AutoConfigurationImportListener> getAutoConfigurationImportLis- teners() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader); }
以上代码首先通过SpringFactoriesLoader类提供的loadFactories方法将spring.factories中配置的接口AutoConfigurationImportListener的实现类加载出来。然后,将筛选出的自动配置类集合和被排除的自动配置类集合封装成AutoConfigurationImportEvent事件对象,并传入该事件对象通过监听器提供的onAutoConfigurationImportEvent方法,最后进行事件广播。关于事件及事件监听相关的内容不在此过多展开。
spring.factories中自动配置监听器相关配置代码如下。
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAuto- ConfigurationImportListener