携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
1. SpringBoot 实现自动装配
我们先看一下 SpringBoot 的核心注解 @SpringBootApplication :
里面的@SpringBootConfiguration上有@Configuration注解,所以实际上也是个配置类
大概可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。根据 SpringBoot 官网,这三个注解的作用分别是:
@EnableAutoConfiguration:启用 SpringBoot 的自动配置机制@Configuration:允许在上下文中注册额外的 bean 或导入其他配置类@ComponentScan: 扫描被@Component(@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilter和AutoConfigurationExcludeFilter。
@EnableAutoConfiguration 是实现自动装配的重要注解,我们以这个注解入手。
1.1. @EnableAutoConfiguration:实现自动装配的核心注解
EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //作用:将main包下的所有组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) //加载自动装配类 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
我们现在重点分析下AutoConfigurationImportSelector 类到底做了什么?
看AutoConfigurationImportSelector的继承图,我们其他先不管,在ImportSeletor中的selectImports方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断自动装配开关是否打开
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 获取所有需要装配的bean
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); // <-- 看这里
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。
该方法调用链如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 第一步
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 第二步
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 第三步
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 第四步
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
第 1 步:
判断自动装配开关是否打开。默认spring.boot.enableautoconfiguration=true,可在 application.properties 或 application.yml 中设置
第 2 步 :
用于获取EnableAutoConfiguration注解中的 exclude 和 excludeName。
第 3 步
获取需要自动装配的所有配置类,读取META-INF/spring.factories
这个文件在springboot-boot-autoconfigure的jar包下,从这里面读取所以的配置类,这些配置类在我们导入了对应的框架的jar包后会自动装载,XXXAutoConfiguration的作用就是按需加载组件。
不光是这个依赖下的META-INF/spring.factories被读取到,所有 Spring Boot Starter 下的META-INF/spring.factories都会被读取到。
如果,我们自己要创建一个 Spring Boot Starter,这一步是必不可少的。
第 4 步 :
到这里可能面试官会问你:“spring.factories中这么多配置,每次启动都要全部加载么?”。
很明显,这是不现实的。我们 debug 到后面你会发现,configurations 的值变小了。
removeDuplicates方法就是转化为LinkedHashSet去重,也就是去掉重复项
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}
getExclusions方法就是选出要排除的项,就是注解中的 exclude 和 excludeName要排除的
checkExcludedClasses返回 SpringFactoriesLoader 用于加载配置候选的类。
configurations.removeAll(exclusions);就是用来移除的
configurations = getConfigurationClassFilter().filter(configurations);就是用来过滤我们没有引入jar包的多余的项
我们怎么知道哪些自动配置类生效?
多亏了@Conditional注解,以及它的派生注解
@ConditionalOnBean:当容器里有指定 Bean 的条件下@ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下@ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean@ConditionalOnClass:当类路径下有指定类的条件下@ConditionalOnMissingClass:当类路径下没有指定类的条件下@ConditionalOnProperty:指定的属性是否有指定的值@ConditionalOnResource:类路径是否有指定的值@ConditionalOnExpression:基于 SpEL 表达式作为判断条件@ConditionalOnJava:基于 Java 版本作为判断条件@ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置@ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下@ConditionalOnWebApplication:当前项目是 Web 项 目的条件下
在这些注解标注的类我们才匹配上
来看filter的代码:
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
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);
}
}
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return result;
}
}
只要不match的,对应candidates候选项就赋为null,就是这样过滤的
那我们看框架是如何得到matches数组的
进入boolean[] match = filter.match(candidates, this.autoConfigurationMetadata)
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
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());
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}
里面有一个outcomes的数组,看看怎么得到的,
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// Split the work and perform half in a background thread if more than one
// processor is available. Using a single additional thread seems to offer the
// best performance. More threads make things worse.
if (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {
return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
}
else {
OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
return outcomesResolver.resolveOutcomes();
}
}
这里面有注释,中文是这样的:
如果有多个处理器可用,则拆分工作并在后台线程中执行一半。使用单个附加线程似乎可以提供最佳性能。更多线程会使事情变得更糟。
所以很好理解,折半分线程操作,我们这里进入的是resolveOutcomesThreaded方法,里面除以2了,
private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
int split = autoConfigurationClasses.length / 2;
OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
autoConfigurationMetadata);
OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
return outcomes;
}
进resolveOutcomes()
public ConditionOutcome[] resolveOutcomes() {
return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}
进getOutcomes
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
AutoConfigurationMetadata autoConfigurationMetadata) {
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;
}
里面获得autoConfigurationClass,看为不为空,单个获得getOutcome
private ConditionOutcome getOutcome(String candidates) {
try {
if (!candidates.contains(",")) {
return getOutcome(candidates, this.beanClassLoader);
}
for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
if (outcome != null) {
return outcome;
}
}
}
catch (Exception ex) {
// We'll get another chance later
}
return null;
}
又看里面有没有逗号分隔,再拆分进入getOutcome的重载
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
if (ClassNameFilter.MISSING.matches(className, classLoader)) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class").items(Style.QUOTE, className));
}
return null;
}
好,现在是最终的getOutcome,这个类是OnClassCondition,所以看匹不匹配类上的ConditionalOnClass.class,也就是@ConditionalOnClass注解,诸如此类。