上一篇我们分析了SpringBoot的条件注解@ConditionalOnXXX的相关源码,总结如下:
SpringBoot的所有@ConditionalOnXxx的条件类OnXxxCondition都是继承于SpringBootCondition基类,而SpringBootCondition又实现了Condition接口。
SpringBootCondition基类主要用来打印一些条件注解评估报告的日志,这些条件评估信息全部来源于其子类注解条件类OnXxxCondition,因此其也抽象了一个模板方法getMatchOutcome留给子类去实现来评估其条件注解是否符合条件。
前一篇还有一个重要的知识点没分析,那就是跟过滤自动配置类逻辑有关的AutoConfigurationImportFilter接口,这篇文章来分析一下。
前面分析了跟SpringBoot的自动配置息息相关内置条件注解@ConditionalOnXxx后,现在就开始来分析跟SpringBoot自动配置的相关源码。
1.@SpringBootApplication注解 思考下,SpringBoot为何一个标注有@SpringBootApplication注解的启动类通过执行一个简单的run方法就能实现SpringBoot大量Starter的自动配置呢?
其实SpringBoot自动配置就跟@SpringBootApplication这个注解有关,先来看下这个注解的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @Target (ElementType.TYPE)@Retention (RetentionPolicy.RUNTIME)@Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan (excludeFilters = { @Filter (type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter (type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { @AliasFor (annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; @AliasFor (annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor (annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor (annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; }
@SpringBootApplication标注了很多注解,可以看到其中跟SpringBoot自动配置有关的注解就只有@EnableAutoConfiguration,因此,可以肯定的是SpringBoot的自动配置肯定跟@EnableAutoConfiguration相关(其中@ComponentScan注解的excludeFilters属性也有一个类AutoConfigurationExcludeFilter,这个类跟自动配置也有点关系,但不是我们关注的重点)。
现在我们来打开@EnableAutoConfiguration注解的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
看到@EnableAutoConfiguration注解又标有@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)两个注解。
@AutoConfigurationPackage注解肯定跟自动配置的包有关,而AutoConfigurationImportSelector则是跟SpringBoot的自动配置选择导入有关(Spring中的ImportSelector是用来导入配置类的,通常是基于某些条件注解@ConditionalOnXxxx来决定是否导入某个配置类)。
因此,需要重点分析AutoConfigurationImportSelector类,因为SpringBoot的自动配置肯定有一个配置类,而这个配置类的导入则需要靠AutoConfigurationImportSelector来实现。
2.SpringBoot自动配置实现逻辑的入口方法 可以肯定的是SpringBoot的自动配置的逻辑肯定与AutoConfigurationImportSelector这个类有关,那么该如何找到SpringBoot自动配置实现逻辑的入口方法呢?
AutoConfigurationImportSelector的相关类图:
可以看到AutoConfigurationImportSelector重点是实现了DeferredImportSelector接口和各种Aware接口,然后DeferredImportSelector接口又继承了ImportSelector接口。
AutoConfigurationImportSelector复写DeferredImportSelector接口的实现方法selectImports方法,因为selectImports方法跟导入自动配置类有关,而这个方法往往是程序执行的入口方法。经过调试发现,selectImports方法跟自动配置相关的逻辑有点关系,但实质关系不大。
又该如何来找到自动配置逻辑有关的入口方法呢?
最简单的方法就是在AutoConfigurationImportSelector类的每个方法都打上断点,然后调试看先执行到哪个方法,但是这样太复杂了。自定义一个Starter的时候,要在spring.factories配置文件中配置
1 EnableAutoConfiguration =XxxAutoConfiguration
因此可以推断,SpringBoot的自动配置原理肯定跟从spring.factories配置文件中加载自动配置类有关,于是结合AutoConfigurationImportSelector的方法注释,我们找到了getAutoConfigurationEntry方法。于是在这个方法里面打上一个断点,此时通过调用栈帧来看下更上层的入口方法在哪里,然后再从跟自动配置相关的更上层的入口方法开始分析。
通过上图可以看到,跟自动配置逻辑相关的入口方法在DeferredImportSelectorGrouping类的getImports方法处,因此就从DeferredImportSelectorGrouping类的getImports方法来开始分析SpringBoot的自动配置源码。
3.分析SpringBoot自动配置原理 既然找到ConfigurationClassParser.getImports()方法是自动配置相关的入口方法,那么下面就来真正分析SpringBoot自动配置的源码。
先看一下getImports方法代码:
1 2 3 4 5 6 7 8 9 10 public Iterable<Group.Entry> getImports() { for (DeferredImportSelectorHolder deferredImport : this .deferredImports) { this .group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } return this .group.selectImports(); }
【1】处的的代码是分析的重点,自动配置相关的大部分逻辑全在这里,深入分析自动配置的主要逻辑。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());主要做的事情就是在this.group即AutoConfigurationGroup对象的process方法中,传入的AutoConfigurationImportSelector对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类。
AutoConfigurationGroup:是AutoConfigurationImportSelector的内部类,主要用来处理自动配置相关的逻辑,拥有process和selectImports方法,然后拥有entries和autoConfigurationEntries集合属性,这两个集合分别存储被处理后的符合条件的自动配置类;
AutoConfigurationImportSelector:承担自动配置的大部分逻辑,负责选择一些符合条件的自动配置类;
metadata:标注在SpringBoot启动类上的@SpringBootApplication注解元数据
【2】的this.group.selectImports的方法主要是针对前面的process方法处理后的自动配置类再进一步有选择的选择导入。
3.1 分析自动配置的主要逻辑 这里继续深究前面【1】处的 this.group.process方法是如何处理自动配置相关逻辑的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert . state( deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String . format("Only %s implementations are supported, got %s" , AutoConfigurationImportSelector .class .getSimpleName() , deferredImportSelector.getClass() .getName() )); AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata () , annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations() ) { this.entries.putIfAbsent(importClassName , annotationMetadata ) ; } }
上面代码中再来看标【1】的方法getAutoConfigurationEntry,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { // 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据, // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class) // 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName="" 的注解数据 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 【1 】得到spring.factories文件配置的所有自动配置类 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 利用LinkedHashSet移除重复的配置类 configurations = removeDuplicates(configurations); // 得到要排除的自动配置类,比如注解属性exclude的配置类 // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class) // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据 Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常 checkExcludedClasses(configurations, exclusions); // 【2 】将要排除的配置类移除 configurations.removeAll(exclusions); // 【3 】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤 // 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,后面会重点分析一下 configurations = filter(configurations, autoConfigurationMetadata); // 【4 】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件, // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类 // 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发 fireAutoConfigurationImportEvents(configurations, exclusions); // 【5 】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回 return new AutoConfigurationEntry(configurations, exclusions); }
AutoConfigurationEntry方法主要做的事情就是获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费。下面总结下AutoConfigurationEntry方法主要做的事情:
【1】从spring.factories配置文件中加载EnableAutoConfiguration自动配置类(注意此时是从缓存中拿到的),获取的自动配置类如下图所示。
【2】若@EnableAutoConfiguration等注解标有要exclude的自动配置类,那么再将这个自动配置类排除掉;
【3】排除掉要exclude的自动配置类后,然后再调用filter方法进行进一步的过滤,再次排除一些不符合条件的自动配置类;
【4】经过重重过滤后,此时再触发AutoConfigurationImportEvent事件,告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类;
【5】 最后再将符合条件的自动配置类返回。
总结了AutoConfigurationEntry方法主要的逻辑后,再来细看一下AutoConfigurationImportSelector的filter方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 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 : getAutoConfigurationImportFilters()) { 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]); } } 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 new ArrayList<>(result); }
AutoConfigurationImportSelector的filter方法主要做的事情就是调用AutoConfigurationImportFilter接口的match方法来判断每一个自动配置类上的条件注解(若有的话)@ConditionalOnClass,@ConditionalOnBean或@ConditionalOnWebApplication是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。
3.2 有选择的导入自动配置类 继续深究前面分析SpringBoot自动配置原理这节标【2】处的 this.group.selectImports方法是如何进一步有选择的导入自动配置类的。直接看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override public Iterable<Entry> selectImports () { if (this .autoConfigurationEntries.isEmpty()) { return Collections.emptyList(); } Set<String > allExclusions = this .autoConfigurationEntries.stream() .map (AutoConfigurationEntry::getExclusions) .flatMap(Collection::stream).collect(Collectors.toSet()); Set<String > processedConfigurations = this .autoConfigurationEntries.stream() .map (AutoConfigurationEntry::getConfigurations) .flatMap(Collection::stream) .collect(Collectors.toCollection(LinkedHashSet::new )); processedConfigurations.removeAll(allExclusions); return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()) .stream() .map ((importClassName) -> new Entry( this .entries.get (importClassName), importClassName)) .collect(Collectors.toList()); }
可以看到,selectImports方法主要是针对经过排除掉exclude的和被AutoConfigurationImportFilter接口过滤后的满足条件的自动配置类再进一步排除exclude的自动配置类,然后再排序。
疑问: 前面已经exclude过一次了,为何这里还要再exclude一次?
4.AutoConfigurationImportFilter 继续分析前面的 AutoConfigurationImportSelector.filter方法的过滤自动配置类的boolean[] match = filter.match(candidates, autoConfigurationMetadata);这行代码。
因此继续分析AutoConfigurationImportFilter接口,分析其match方法,同时也是对前一篇@ConditionalOnXxx的源码分析文章中留下的问题进行补充。
AutoConfigurationImportFilter接口只有一个match方法用来过滤不符合条件的自动配置类。
1 2 3 4 5 @FunctionalInterface public interface AutoConfigurationImportFilter { bool ean[] match(String[] auto ConfigurationClasses, AutoConfigurationMetadata auto ConfigurationMetadata); }
在分析AutoConfigurationImportFilter接口的match方法前,先看下类关系图:
可以看到AutoConfigurationImportFilter接口有一个具体的实现类FilteringSpringBootCondition,FilteringSpringBootCondition又有三个具体的子类:OnClassCondition,OnBeanCondtition和OnWebApplicationCondition。
那么这几个类之间的关系是怎样的呢?
FilteringSpringBootCondition实现了AutoConfigurationImportFilter接口的match方法,然后在FilteringSpringBootCondition的match方法调用getOutcomes这个抽象模板方法返回自动配置类的匹配与否的信息。同时,最重要的是FilteringSpringBootCondition的三个子类OnClassCondition,OnBeanCondtition和OnWebApplicationCondition将会复写这个模板方法实现自己的匹配判断逻辑。
好了,AutoConfigurationImportFilter接口的整体关系已经清楚了,现在再进入其具体实现类FilteringSpringBootCondition的match方法看看是其如何根据条件过滤自动配置类的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override 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 ; }
FilteringSpringBootCondition的match方法主要做的事情还是调用抽象模板方法getOutcomes来根据条件来过滤自动配置类,而复写getOutcomes模板方法的有三个子类,这里不再一一分析,只挑选OnClassCondition复写的getOutcomes方法进行分析。
4.1 OnClassCondition OnClassCondition复写的getOutcomes方法的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override protected final ConditionOutcome[] getOutcomes(String[] auto ConfigurationClasses, AutoConfigurationMetadata auto ConfigurationMetadata) { int split = auto ConfigurationClasses.length / 2 ; OutcomesResolver firstHalfResolver = createOutcomesResolver(auto ConfigurationClasses, 0 , split, auto ConfigurationMetadata); OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(auto ConfigurationClasses, split, auto ConfigurationClasses.length, auto ConfigurationMetadata, getBeanClassLoader()); ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes(); ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes(); ConditionOutcome[] outcomes = new ConditionOutcome[auto ConfigurationClasses.length]; System.array copy(firstHalf, 0 , outcomes, 0 , firstHalf.length); System.array copy(secondHalf, 0 , outcomes, split, secondHalf.length); return outcomes; }
可以看到,OnClassCondition的getOutcomes方法主要解析自动配置类是否符合匹配条件,当然这个匹配条件指自动配置类上的注解@ConditionalOnClass指定的类存不存在于classpath中,存在则返回匹配,不存在则返回不匹配。
由于解析自动配置类是否匹配比较耗时,因此从上面代码中我们可以看到分别创建了firstHalfResolver和secondHalfResolver两个解析对象,这两个解析对象个分别对应一个线程去解析加载的自动配置类是否符合条件,最终将两个线程的解析自动配置类的匹配结果合并后返回。
那么自动配置类是否符合条件的解析判断过程又是怎样的呢?现在我们分别来看一下上面代码注释标注的【1】,【2】,【3】和【4】处。
4.1.1 createOutcomesResolver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private OutcomesResolver createOutcomesResolver (String [] autoConfigurationClasses, int start, int end , AutoConfigurationMetadata autoConfigurationMetadata) { OutcomesResolver outcomesResolver = new StandardOutcomesResolver( autoConfigurationClasses, start, end , autoConfigurationMetadata, getBeanClassLoader()); try { return new ThreadedOutcomesResolver(outcomesResolver); } catch (AccessControlException ex) { return outcomesResolver; } }
可以看到createOutcomesResolver方法创建了一个封装了StandardOutcomesResolver类的ThreadedOutcomesResolver解析对象。再来看下ThreadedOutcomesResolver这个线程解析类封装StandardOutcomesResolver这个对象的目的是什么?继续跟进代码:
1 2 3 4 5 6 7 8 private ThreadedOutcomesResolver (OutcomesResolver outcomesResolver ) { this .thread = new Thread( () -> this .outcomes = outcomesResolver.resolveOutcomes()); this .thread.start(); }
可以看到在构造ThreadedOutcomesResolver对象时候,原来是开启了一个线程,然后这个线程其实还是调用了刚传进来的StandardOutcomesResolver对象的resolveOutcomes方法去解析自动配置类。
4.1.2 new StandardOutcomesResolver() 逻辑很简单,就是创建了一个StandardOutcomesResolver对象,用于后面解析自动配置类是否匹配,同时,新建的一个线程也是利用它来完成自动配置类的解析的。
4.1.3 StandardOutcomesResolver.resolveOutcomes() 这个方法承担了解析自动配置类匹配与否的全部逻辑,是需要重点分析的方法,resolveOutcomes方法最终把解析的自动配置类的结果赋给secondHalf数组。那么它是如何解析自动配置类是否匹配条件的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Override public ConditionOutcome[] resolveOutcomes() { return getOutcomes(this .auto ConfigurationClasses, this .start, this .end, this .auto ConfigurationMetadata); } private ConditionOutcome[] getOutcomes(String[] auto ConfigurationClasses, int start, int end, AutoConfigurationMetadata auto ConfigurationMetadata) { ConditionOutcome[] outcomes = new ConditionOutcome[end - start]; for (int i = start; i < end; i++) { String auto ConfigurationClass = auto ConfigurationClasses[i]; if (auto ConfigurationClass != null ) { String candidates = auto ConfigurationMetadata.get (auto ConfigurationClass, "ConditionalOnClass" ); if (candidates != null ) { outcomes[i - start] = getOutcome(candidates); } } } return outcomes; }
可以看到StandardOutcomesResolver.resolveOutcomes
的方法中再次调用getOutcomes
方法,主要是从autoConfigurationMetadata
对象中获取到自动配置类上的注解@ConditionalOnClass
指定的类的全限定名,然后作为参数传入getOutcome
方法用于去类路径加载该类,若能加载到则说明注解@ConditionalOnClass
满足条件,此时说明自动配置类匹配成功。
但是别忘了,这里只是分析了@ConditionalOnClass
注解,若自动配置类还有其他注解比如@ConditionalOnBean
,若该@ConditionalOnBean
注解不满足条件的话,同样最终结果是不匹配的。回到OnClassCondtion
的判断逻辑,继续进入getOutcome
方法看它是如何去判断@ConditionalOnClass
注解满不满足条件的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private ConditionOutcome getOutcome(String candidates ) { try { if (!candidates.contains("," )) { return getOutcome(candidates , ClassNameFilter.MISSING, this .beanClassLoader ) ; } for (String candidate : StringUtils . commaDelimitedListToStringArray(candidates ) ) { ConditionOutcome outcome = getOutcome(candidate , ClassNameFilter.MISSING, this .beanClassLoader ) ; if (outcome != null) { return outcome; } } } catch (Exception ex) { } return null; }
可以看到,getOutcome
方法再次调用重载方法getOutcome
进一步去判断注解@ConditionalOnClass
指定的类存不存在类路径中,跟着主线继续跟进去:
1 2 3 4 5 6 7 8 9 10 11 12 13 private ConditionOutcome getOutcome(String className , ClassNameFilter classNameFilter , ClassLoader classLoader ) { if (classNameFilter.matches(className, classLoader)) { return ConditionOutcome . noMatch(ConditionMessage.forCondition (ConditionalOnClass.class ) .didNotFind("required class" ) .items(Style.QUOTE, className)); } return null; }
一层一层的剥,最终剥到了最底层了,这个真的需要足够耐心,没办法,源码只能一点一点的啃。可以看到最终是调用ClassNameFilter
的matches
方法来判断@ConditionalOnClass
指定的类存不存在类路径中,若不存在的话,则返回不匹配。
继续跟进ClassNameFilter
的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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
原来是FilteringSpringBootCondition
的一个内部枚举类,实现了判断指定类是否存在于classpath
中的逻辑。
4.1.4 ThreadedOutcomesResolver.resolveOutcomes() 这里是用新开启的线程去调用StandardOutcomesResolver.resolveOutcomes
方法解析另一半自动配置类是否匹配,因为是新线程,这里很可能会出现这么一种情况:主线程解析完属于自己解析的一半自动配置类后,那么就继续往下跑了,此时不会等待新开启的子线程的。
因此,为了让主线程解析完后,需要让主线程继续等待正在解析的子线程,直到子线程结束。那么继续跟进代码区看下ThreadedOutcomesResolver.resolveOutcomes
方法是怎样实现让主线程等待子线程的:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public ConditionOutcome[] resolveOutcomes() { try { this .thread.join(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } return this .outcomes; }
可以看到用了Thread.join()
方法来让主线程等待正在解析自动配置类的子线程,这里应该也可以用CountDownLatch
来让主线程等待子线程结束。最终将子线程解析后的结果赋给firstHalf
数组。
4.2 OnBeanCondition 和 OnWebApplicationCondition 前面深入分析了OnClassCondition
是如何过滤自动配置类的,那么自动配置类除了要经过OnClassCondition
的过滤,还要经过OnBeanCondition
和OnWebApplicationCondition
这两个条件类的过滤,可自行分析。
5.AutoConfigurationImportListener 继续分析前面AutoConfigurationImportSelector.getAutoConfigurationEntry
方法的触发自动配置类过滤完毕的事件fireAutoConfigurationImportEvents(configurations, exclusions);
这句代码。
直接点进fireAutoConfigurationImportEvents
方法看看其是如何触发事件的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 ) ; } } }
如上,fireAutoConfigurationImportEvents
方法做了以下两件事情:
调用getAutoConfigurationImportListeners
方法从spring.factories
配置文件获取实现AutoConfigurationImportListener
接口的事件监听器;如下图,可以看到获取的是ConditionEvaluationReportAutoConfigurationImportListener
:
遍历获取的各个事件监听器,然后调用监听器各种Aware
方法给监听器赋值,最后再依次回调事件监听器的onAutoConfigurationImportEvent
方法,执行监听事件的逻辑。
此时再来看下ConditionEvaluationReportAutoConfigurationImportListener
监听器监听到事件后,它的onAutoConfigurationImportEvent
方法究竟做了哪些事情:
1 2 3 4 5 6 7 8 9 10 11 12 @Override public void onAutoConfigurationImportEvent (AutoConfigurationImportEvent event ) { if (this .beanFactory != null ) { ConditionEvaluationReport report = ConditionEvaluationReport.get (this .beanFactory); report.recordEvaluationCandidates(event .getCandidateConfigurations()); report.recordExclusions(event .getExclusions()); } }
可以看到,ConditionEvaluationReportAutoConfigurationImportListener
监听器监听到事件后,做的事情很简单,只是分别记录下符合条件和被exclude
的自动配置类。
6.AutoConfigurationPackages 前面已经详述了SpringBoot的自动配置原理了,最后,跟SpringBoot自动配置有关的注解@AutoConfigurationPackage
还没分析,来看下这个注解的源码:
1 2 3 4 5 6 7 8 @Target (ElementType.TYPE)@Retention (RetentionPolicy.RUNTIME)@Documented @Inherited @Import (AutoConfigurationPackages.Registrar.class)public @interface AutoConfigurationPackage { }
可以看到@AutoConfigurationPackage
注解是跟SpringBoot自动配置所在的包相关的,即将 添加该注解的类所在的package 作为 自动配置package 进行管理。
接下来我们再看看AutoConfigurationPackages.Registrar
类是干嘛的,直接看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static class Registrar implements ImportBeanDefinitionRegistrar , DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set <Object > determineImports (AnnotationMetadata metadata ) { return Collections.singleton(new PackageImport(metadata)); } }
可以看到Registrar
类是AutoConfigurationPackages
的静态内部类,实现了ImportBeanDefinitionRegistrar
和DeterminableImports
两个接口。现在主要来关注下Registrar
实现的registerBeanDefinitions
方法,这个方法是注册bean
定义的方法。看到它又调用了AutoConfigurationPackages
的register
方法,继续跟进源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void register(BeanDefinitionRegistry registry, String... packageNames) { if (registry.containsBeanDefinition(BEAN)) { BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition .getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BEAN, beanDefinition); }}
如上,可以看到register
方法注册了一个packageNames
即自动配置类注解@EnableAutoConfiguration
所在的所在的包名相关的bean
。那么注册这个bean
的目的是为了什么呢?
结合注释知道,注册这个自动配置包名相关的bean
是为了被其他地方引用,比如JPA entity scanner
。
7.总结 最后,再总结下SpringBoot自动配置的原理,主要做了以下事情:
从spring.factories配置文件中加载自动配置类;
加载的自动配置类中排除掉@EnableAutoConfiguration
注解的exclude
属性指定的自动配置类;
然后再用AutoConfigurationImportFilter
接口去过滤自动配置类是否符合其标注注解(若有标注的话)@ConditionalOnClass
,@ConditionalOnBean
和@ConditionalOnWebApplication
的条件,若都符合的话则返回匹配结果;
然后触发AutoConfigurationImportEvent
事件,告诉ConditionEvaluationReport
条件评估报告器对象来分别记录符合条件和exclude
的自动配置类。
最后spring再将最后筛选后的自动配置类导入IOC容器中
疑问:
为了避免加载不必要的自动配置类造成内存浪费,FilteringSpringBootCondition
用于过滤spring.factories
文件的自动配置类,而FilteringSpringBootCondition
为啥只有OnOnBeanCondition
,OnClassCondition
和onWebApplicationCondition
这三个条件类用于过滤,为啥没有onPropertyCondtion
,onResourceCondition
等条件类来过滤自动配置类呢?
1 2 3 4 5 # Auto Configuration Import Filters org.springframework.boot.auto configure.AutoConfigurationImportFilter=\ org.springframework.boot.auto configure.condition.OnBeanCondition,\ org.springframework.boot.auto configure.condition.OnClassCondition,\ org.springframework.boot.auto configure.condition.OnWebApplicationCondition
参考资料
https://blog.csdn.net/ttyy1112/article/details/101284541
评论加载中