AutoConfiguration排除指定组件
在 上节中我们获得了 spring.factories 文件中注册的自动加载组件,但如果在实际应用的过程中并不需要其中的某个或某些组件,可通过配置@EnableAutoConfiguration 的注解属性 exclude 或 excludeName 进行有针对性的排除,当然也可以通过配置文件进行排除。先通过源代码看看如何获取排除组件的功能。
代码语言:javascript复制protected Set<String> getExclusions (AnnotationMetadata metadata,
AnnotationAttributes attributes) {
//创建 Set 集合并把待排除的内容存于集合内,LinkedHashSet 具有不可重复性
Set<String> excluded = new L inkedHashSet<>();
excluded . addAll(asList(attributes, "exclude"));
excluded . addAll(Arrays. asList(attributes. getStringArray(" excludeName")));
exc. luded. addAll(getExcludeAutoConf igurationsProperty());
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 . emptyLi
st();
}
AutoConfigurationlmportSelector 中通过调用 getExclusions 方法来获取被排除类的集合。
它会收集@EnableAutoConfiguration 注解中配置的 exclude 属性值 excludeName 属性值 并 通 过 方 法 getExcludeAutoConfigurationsProperty 获 取 在 配 置 文 件 中 key 为spring.autoconfigure.exclude 的配置值。
以排除自动配置 DataSourceAutoConfiguration 为例,配置文件中的配置形式如下。
spring. autoconfigure . exc lude=org. spr ingframework . boot . autoconfigure .jdbc .DataSource-AutoConfiguration
获取到被排除组件的集合之后,首先是对待排除类进行检查操作,代码如下。
代码语言:javascript复制private void checkExcludedClasses(List<String> configurat ions,
Set<String> exclusions) {
List<String> invalidExcludes = new ArrayL ist<>(exclusions. size());
//遍历并判断是否存在对应的配置类
for (String exclusion : exclusions) {
if (ClassUtils. isPresent(exclusion, getClass(). getClassLoader())
&& !configurat ions . contains(exclusion)) {
inval idExcludes . 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 classe
s:%n%s", message));
}
以上代码中,checkExcludedClasses 方 法用来确保被排除的类存在于当前的 ClassLoader中 , 并 且 包 含 在 spring.factories 注 册 的 集 合 中 。如 果 不 满 足 以 上 条 件 , 调 用handleInvalidExcludes 方法抛出异常。
如果被排除类都符合条件,调用 configurations.removeAll(exclusions)方法从自动配置集合中移除被排除集合的类,至此完成初步的自动配置组件排除。
AutoConfiguration 过滤自动配置组件
当完成初步的自动配置组件排除工作之后 AutoConfigurationlmportSelector 会结合在此之前获取的 AutoConfigurationMetadata 对象,对组件进行再次过滤。
代码语言:javascript复制private List<String> filter(List<String> configurations,
AutoConf igurationMetadata autoConfigurationMeta
data)
long startTime = System. nanoTime();
String[] candidates = StringUtils. toStringArray( configurations);
boolean[] skip = new boolean[ candidates . length];
boolean skipped = false;
for (AutoConfigurationImportFilter filter : getAutoConf igurationImportFil
te-
rs()) {
invokeAwareMethods(filter);
boolean[] match = filter . match(candidates, autoConfigurat ionMetadata);
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 ArrayL ist<>(result);
}
protected List<AutoConfigurat ionImportFilter> getAutoConfigurat ionImportFil
ters() {
return SpringFactoriesLoader . loadFactories (AutoConfigurat ionImportFilter.
class,
this . beanClassLoader);}
下面,我们先来明确一一下都有哪些数据参与 了以上两种方法,然后再进行业务逻辑的梳理。
-configurations: List, 经过初次过滤之后的自动配置组件列表。
-autoConfigurationMetadata: AutoConfigurationMetadata ,元数据文件META-INF/spring-autoconfigure-metadata.properties 中配置对应实体类。
List :META-INF/springfactories 中配置 key 为 Auto-ConfigurationImportFilter 的 Filters 列表。
getAutoConfigurationImportFilters 方法是通过 SpringFactoriesLoader 的 loadFactories 方法将 ME TA-INF/spring.factories 中配置 key 为 AutoConfigurationlmportFilter 的值进行加载。下面为 META-INF/spring.factories 中相关配置的具体内容。
代码语言:javascript复制# Auto Configuration Import Filters
org. springframework. boot . autoconfigure . AutoConfigurationImportFilter=
org. springframework . boot . autoconfigure. condit ion . OnBeanCondition,
org . spr ingframework . boot . autoconfigure . condition . OnClassCondition,
org. springframework. boot . autoconfigure . condit ion . OnWebApplicat ionCondition
在 spring-boot-autoconfigure 中 默 认 配 置 了 3 个 筛 选 条 件 , OnBeanCondition 、OnClassCondition和OnWebApplicationCondition,它们均实现了 AutoConfigurationlmportFilter 接口。
在明确了以上信息之后,该 filter 方法的过滤功能就很简单了。用一句话来概述就是:对自动配 置 组 件 列 表 进 行 再 次 过 滤 , 过 滤 条 件 为 该 列 表 中 自 动 配 置 类 的 注 解 得 包 含 在 OnBeanConditionOnClassCondition 和 OnWebApplicationCondition 中指定的注解依次包含@ConditionalOnBean@ConditionalOnClass 和@ConditionalOnWebApplication.那 么 在 这 个 实 现 过 程 中 , AutoConfigurationMetadata 对应 的 元 数 据 和AutoConfiguration-lmportFilter 接口及其实现类是如何进行具体筛选的呢?我们先来看一下AutoConfiguration-lmportFilter 接口相关类的结构及功能, 如图 2-4 所示。
下面进行相关的源代码及步骤的分解。我们已经知道 AutoConfigurationlmportFilter 接口可以在 spring.factories 中注册过滤器,用来过滤自动配置类,在实例化之前快速排除不需要的自动配置,代码如下。
代码语言:javascript复制@FunctionalInterface
public interface AutoConfigurationImportFilter {
boolean[] match(String[ ] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurat ionMetadata);
}
match 方法接收两个参数,一个是待过滤的自动配置类数组,另一个是自动配置的元数据信息 。match 返 回 的 结 果 为 匹 配 过 滤 后 的 结 果 布 尔 数 组 , 数 组 的 大 小 与 String[ ]autoConfigurationClasses-致, 如果需排除,设置对应值为 false。
图 2-4 中已经显示 AutoConfigurationlmportFilter 接口的 match 方法主要在其抽象子类中实现,而抽象子类 FilteringSpringBootCondition 在实现 match 方法的同时又定义了新的抽象方法 getOutcomes,继承该抽象类的其他 3 个子类均实现了 getOutcomes 方法,代码如下。
代码语言:javascript复制abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanF actoryAware, BeanClassLoad
er
Aware {
@Override
public boolean[] match(String[] autoConfigurationClasses,
AutoConfigurat ionMetadata autoConfigurat ionMetad
ata) {
ConditionOutcome[] outcomes = getOutcomes( autoConfigurationClasses,
autoConfigurat ionMetadata);
boolean[] match = new boolean[outcomes. length];
for (int i = 0; i < outcomes. length; i ) {
match[i] = (outcomes[i] == nu1l|| outcomes[i]. isMatch());
return match;
//过滤核心功能,该方法由子类实现
protected abstract ConditionOutcome[] get0utcomes(String[ ] autoConfigur
ationClasses,
AutoConfigurat ionMetada
ta autoConfigurationMetadata);
}
通过上面的源码可以看出,match 方法在抽象类 FilteringSpringBootCondition 中主要的功能就是调用 getOutcomes 方法,并将其返回的结果转换成布尔数组。而相关的过滤核心功能由子类实现的 getOutcomes 方法来实现。
下面以实现类 OnClassCondition 来具体说明执行过程。首先看一下入口方法 getOutcomes的源代码。
代码语言:javascript复制@Order (Ordered . HIGHEST_ PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
@Override
protected final ConditionOutcome[] get0utcomes (String[] autoConfiguration
Classes ,
AutoConfigurationMetadata
autoConfigurationMetadata) {
//如果有多个处理器,采用后台线程处理
if (Runtime . getRuntime(). availableProcessors() > 1) {
return resolveOutcomes Threaded( autoConfigurationClasses, autoConfigu-
rationMetadata);
} else {
OutcomesResolver outcomesResolver = new
StandardoutcomesResolver ( autoConfigurationClasses, 0,
autoConfigurationClasses.length, autoConfigu
rationMetadata,
getBeanClassLoader());
return outcomesResolver . resolve0utcomes( );
}
}
}
Spring Boot 当前版本对 getOutcomes 方法进行了性能优化,根据处理器的情况不同采用了不同的方式进行操作。如果使用多个处理器,采用后台线程处理(之前版本的实现方法)。否则,getOutcomes 直接创建 StandardOutcomesResolver 来处理。
在 resolveOutcomesThreaded 方法中主要采用了分半处理的方法来提升处理效率,而核心功能都是在内部类 StandardOutcomesResolver 的 resolveOutcomes 方法中实现。
resolveOutcomes Threaded 的分半处理实现代码如下。
代码语言:javascript复制@Order(Ordered . HIGHEST_ PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
private ConditionOutcome[ ] resolveOutcomesThreaded(String[ ] autoConfigura
tionClasses,
AutoConfigurationMetad
ata autoConfigurationMetadata) {
int split = autoConfigurationClasses. length / 2;
OutcomesResolver firstHalfResolver = create0utcomesResolver( autoConfigu
rationClass
es, 0,split, autoConfigurat ionMetadata) ;
OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(au-
toCo
nfigurationClasses, split,
auto
ConfigurationClasses. length, autoConfigurat ionMetadata, getBean-Clas
sLoader());
ConditionOutcome[ ] secondHalf = secondHalfResolver . resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver . resolve0utcomes( );
ConditionOutcome[] outcomes = new ConditionOutcome[ autoConfigurationCla
ses. length];
System. arraycopy(firstHalf, 0,outcomes, 0,firstHalf .length);
System. arraycopy(secondHalf, 0,outcomes, split, secondHalf . length);
return outcomes ;
}
}
内部类 StandardOutcomesResolver 的源代码重点关注 getOutcomes 方法的实现,它实现了获取元数据中的指定配置,间接调用 getOutcome(StringclassName,ClassL oader classLoader)方法来判断该类是否符合条件,部分源代码如下。
代码语言:javascript复制@Order(Ordered. HIGHEST_ PRECEDENCE)
class OnClassCondition extends FilteringSpr ingBootCondition {
private final class StandardOutcomesResolver implements OutcomesResolver
private ConditionOutcome[] getOutcomes(String[ ] autoConfigurationClasse
int start, int end, AutoConfigur
ationMetadata autoConfiguration-
Metadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
for(inti=start;i<end;i ){
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
String candidates = autoConfigurationMetadata
. get( autoConfigurat ionClass, "Conditional0nClass");
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(StringclassName,String key) 方法,而该方法由类 AutoConfigurationMetadataL oader 来实现。
该类在前面的章节已经提过了,它会加载 META-INF/spring-autoconfigure-metadata.properties 中的配置。
代码语言:javascript复制final class AutoConfigurat ionMetadataLoader {
protected static final String PATH = "META-INF/"
" spring- autoconfigure - metadata. properties";
private static class PropertiesAutoConfigurat ionMetadata
implements AutoConfigurat ionMetadata {
@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;
}
}
}
AutoConfigurationMetadataL oader 的内部类 PropertiesAutoConfigurationMetadata 实现了 AutoConfigurationMetadata 接 口 的 具 体 方 法 , 其 中 包 含 我 们 用 到 的 get(StringclassName,String key)方法。
根据 get 方法实现过程,我们不难发现,在 getOutcomes 方 法中获取到的 candidates,其实就是 META-INF/spring-autoconfigure-metadata.properties 文件中配置的 key 为自动加载注解类 "." "ConditionalOnClass"的字符串,而 value 为其获得的值。以数据源的自动配置为例,寻找到的对应元数据配置如 org. springframework . boot . autoconf igure . jdbc .DataSourceAutoConfiguration. ConditionalOnClass=javax . sql. DataSource , org.springframework. jdbc . datasource. embedded.EmbeddedDatabaseTypekey为自动0载组件org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration加上"."”,再加上当前过滤条件中指定的 ConditionalOnClass。
然后,根据此 key 获得的 value 直为 javax.sql.DataSource, org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType.当 获 取 到 对 应 的candidates值 之 后 , 最 终 会 调 用getOutcome(StringclassName,ClassLoader classLoader)方法 ,并在其中使用枚举类ClassNameFilter.MISSING 的 matches 方法来判断 candidates 值是否匹配。而枚举类ClassNameFilter 位于 OnClassCondition 继承的 抽象类 FilteringSpringBootCondition 中。
代码语言:javascript复制abstract class FilteringSpringBootCondition extends SpringBootConditionD
implements AutoConfigurationImportFilter, BeanF actoryAware, BeanClassLoad
erAware {
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 lisPresent(className, classLoader);
};
public abstract boolean matches (String className, ClassLoader classLoad
er);
//通过类加载是否抛出异常来判断该类是否存在
public static boolean isPresent(String className, ClassLoader classLoad
er) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}try{
forName (className, classLoader);
return true;
} catch (Throwable ex) {
return false;
}
//进行类加载操作private static Class<?> forName ( String className, ClassLoader classLoad
er)
throws Clas sNotFoundException {
if (classLoader != null) {
return classLoader . loadClass(className);
return Class . forName( className);
}
}
ClassNameFilter 的匹配原则很简单,就是通过类加载器去加载指定的类。如果指定的类加载成功,即没有抛出异常,说明 ClassNameFilter 匹配成功。如果抛出异常,说明ClassNameFilter 匹配失败。
至此,整个过滤过程的核心部分已经完成了。我们再用一-张简单的流程图来回顾整个过滤的过程,如图2-5所示。
本文给大家讲解的内容是AutoConfiguration排除指定组件和过滤自动配置组件
- 下篇文章给大家讲解的是AutoConfiguration事件注册和@Conditional 条件注解、实例解析;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。