2. SpringBoot源码分析 2.1 源码分析-依赖管理 1. 问题一:springboot为什么不需要指定版本? SpringBoot项目pom.xml中有两个核心依赖:spring-boot-starter-parent和spring-boot-starter-web。
spring-boot-starter-parent
在chapter01项目中的pom.xml文件中找到spring-boot-starter-parent依赖,示例代码如下:
<parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.9.RELEASE</version > <relativePath /> </parent >
上述代码中,将spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理,并将项目版本号统一为2.2.9.RELEASE,该版本号根据实际开发需求是可以修改的
使用“Ctrl+鼠标左键”进入并查看spring-boot-starter-parent底层源文件,先看spring-boot-starter-parent做了哪些事 首先看spring-boot-starter-parent 的properties 节点
<properties > <main.basedir > ${basedir}/../../..</main.basedir > <java.version > 1.8</java.version > <resource.delimiter > @</resource.delimiter > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <project.reporting.outputEncoding > UTF-8</project.reporting.outputEncoding > <maven.compiler.source > ${java.version}</maven.compiler.source > <maven.compiler.target > ${java.version}</maven.compiler.target > </properties >
在这里spring-boot-starter-parent 定义了:
工程的Java版本为1.8 。 工程代码的编译源文件编码格式为UTF-8 工程编译后的文件编码格式为UTF-8 Maven打包编译的版本 再来看spring-boot-starter-parent 的「build」节点 接下来看POM的build 节点,分别定义了resources 资源和pluginManagement
<resources > <resource > <filtering > true</filtering > <directory > ${basedir}/src/main/resources</directory > <includes > <include > **/application*.yml</include > <include > **/application*.yaml</include > <include > **/application*.properties</include > </includes > </resource > <resource > <directory > ${basedir}/src/main/resources</directory > <excludes > <exclude > **/application*.yml</exclude > <exclude > **/application*.yaml</exclude > <exclude > **/application*.properties</exclude > </excludes > </resource > </resources >
我们详细看一下resources 节点,里面定义了资源过滤,针对application 的yml 、properties 格式进行了过滤,可以支持不同环境的配置,比如application-dev.yml 、application-test.yml 等等。
pluginManagement 则是引入了相应的插件和对应的版本依赖
最后来看spring-boot-starter-parent的父依赖spring-boot-dependencies的properties节点
我们看定义POM,这个才是SpringBoot项目的真正管理依赖的项目,里面定义了SpringBoot相关的版本
<properties > <main.basedir > ${basedir}/../..</main.basedir > <activemq.version > 5.15.13</activemq.version > <antlr2.version > 2.7.7</antlr2.version > <appengine-sdk.version > 1.9.81</appengine-sdk.version > <artemis.version > 2.10.1</artemis.version > <aspectj.version > 1.9.6</aspectj.version > <assertj.version > 3.13.2</assertj.version > <atomikos.version > 4.0.6</atomikos.version > <awaitility.version > 4.0.3</awaitility.version > <bitronix.version > 2.1.4</bitronix.version > <byte-buddy.version > 1.10.13</byte-buddy.version > <caffeine.version > 2.8.5</caffeine.version > <cassandra-driver.version > 3.7.2</cassandra-driver.version > <classmate.version > 1.5.1</classmate.version > ....... </properties >
spring-boot-dependencies的dependencyManagement节点在这里,dependencies定义了SpringBoot版本的依赖的组件以及相应版本。
<dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot</artifactId > <version > ${revision}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-test</artifactId > <version > ${revision}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-test-autoconfigure</artifactId > <version > ${revision}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-actuator</artifactId > <version > ${revision}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-actuator-autoconfigure</artifactId > <version > ${revision}</version > </dependency > ...... </dependencies > </dependencyManagement >
spring-boot-starter-parent 通过继承spring-boot-dependencies 从而实现了SpringBoot的版本依赖管理,所以我们的SpringBoot工程继承spring-boot-starter-parent后已经具备版本锁定等配置了,这也就是在 Spring Boot 项目中部分依赖不需要写版本号的原因
2. spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来的? spring-boot-starter-web
查看spring-boot-starter-web依赖文件源码,核心代码具体如下
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-validation</artifactId > <exclusions > <exclusion > <groupId > org.apache.tomcat.embed</groupId > <artifactId > tomcat-embed-el</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-web</artifactId > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > </dependency >
从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是打包了Web开发场景所需的底层所有依赖(基于依赖传递,当前项目也存在对应的依赖jar包) 正是如此,在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发,而不需要额外导入Tomcat服务器以及其他Web依赖文件等。 当然,这些引入的依赖文件的版本号还是由spring-boot-starter-parent父依赖进行的统一管理。
Spring Boot除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖,我们可以打开Spring Boot官方文档,搜索“Starters”关键字查询场景依赖启动器。
列出了Spring Boot官方提供的部分场景依赖启动器,这些依赖启动器适用于不同的场景开发,使用时只需要在pom.xml文件中导入对应的依赖启动器即可。
需要说明的是,Spring Boot官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如阿里巴巴的Druid数据源等,Spring Boot官方就没有提供对应的依赖启动器。为了充分利用Spring Boot框架的优势,在Spring Boot官方没有整合这些技术框架的情况下,Druid等技术框架所在的开发团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器,例如druid-spring-boot-starter等。我们在pom.xml文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号。
2.2 源码分析-自动配置 自动配置:根据我们添加的jar包依赖,会自动将一些配置类的bean注册进ioc容器,我们可以需要的地方使用@autowired或者@resource等注解来使用它。
问题 :Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?
Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,
@SpringBootApplication
: SpringBoot 应用标注在某个类上说明这个类是SpringBoot 的主配置类, SpringBoot 就应该运行这个类的main() 方法启动SpringBoot 应用。
1. @SpringBootApplication 下面,查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下
@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注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下:
2. @SpringBootConfiguration @SpringBootConfiguration : SpringBoot 的配置类,标注在某个类上,表示这是一个SpringBoot的配置类。
查看@SpringBootConfiguration注解源码,核心代码具体如下。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration {}
从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已。
3. @EnableAutoConfiguration package org.springframework.boot.autoconfigure;@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
Spring 中有很多以Enable 开头的注解,其作用就是借助@Import 来收集并注册特定场景相关的Bean ,并加载到IOC 容器。 @EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。
4. @AutoConfigurationPackage package org.springframework.boot.autoconfigure;@Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {}
@AutoConfigurationPackage
:自动配置包,它也是一个组合注解,其中最重要的注解是@Import(AutoConfigurationPackages.Registrar.class)
,它是Spring 框架的底层注解,它的作用就是给容器中导入某个组件类,例如@Import(AutoConfigurationPackages.Registrar.class)
,它就是将Registrar
这个组件类导入到容器中,可查看Registrar 类中registerBeanDefinitions
方法:
@Override public void registerBeanDefinitions (AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); }
我们对new PackageImport(metadata).getPackageName() 进行检索,看看其结果是什么?
再看register方法
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); } }
AutoConfigurationPackages.Registrar这个类就干一个事,注册一个Bean ,这个Bean 就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有一个参数,这个参数是使用了@AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给JPA entity 扫描器用来扫描开发人员通过注解@Entity 定义的entity类。
5. @Import(AutoConfigurationImportSelector.class) @Import({AutoConfigurationImportSelector.class})
:将AutoConfigurationImportSelector 这个类导入到Spring 容器中,AutoConfigurationImportSelector 可以帮助Springboot 应用将所有符合条件的@Configuration配置都加载到当前SpringBoot 创建并使用的IOC 容器( ApplicationContext )中。
可以看到AutoConfigurationImportSelector 重点是实现了DeferredImportSelector 接口和各种 Aware 接口,然后DeferredImportSelector 接口又继承了ImportSelector 接口。
其不光实现了ImportSelector 接口,还实现了很多其它的Aware 接口,分别表示在某个时机会被回调。
确定自动配置实现逻辑的入口方法 :
跟自动配置逻辑相关的入口方法在DeferredImportSelectorGrouping 类的getImports 方法处,因此我们就从DeferredImportSelectorGrouping 类的getImports 方法来开始分析SpringBoot的自动配置源码好了。
先看一下getImports 方法代码:
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方法处理后的自动配置类再进一步有选择的选择导入
再进入到AutoConfigurationImportSelector$AutoConfigurationGroup的process方法:
通过图中我们可以看到,跟自动配置逻辑相关的入口方法在process方法中。
分析自动配置的主要逻辑 :
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 ,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。直接上代码:
protected AutoConfigurationEntry getAutoConfigurationEntry ( AutoConfigurationMetadata autoConfigurationMetadata, 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 = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
**深入 getCandidateConfigurations 方法 **
这个方法中有一个重要方法loadFactoryNames ,这个方法是让SpringFactoryLoader 去加载一些组件的名字。
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;
继续点开loadFactory 方法
public static List<String> loadFactoryNames (Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null ) { return result; } else { try { Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories" ) : ClassLoader.getSystemResources("META-INF/spring.factories" ); LinkedMultiValueMap result = new LinkedMultiValueMap(); while (urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while (var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for (int var11 = 0 ; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } } }
从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。
spring.factories里面保存着springboot的默认提供的自动配置类。
META-INF/spring.factories
AutoConfigurationEntry 方法主要做的事情就是获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费。我们下面总结下AutoConfigurationEntry 方法主要做的事情: 【1】从spring.factories 配置文件中加载EnableAutoConfiguration 自动配置类),获取的自动配置类如图所示。 【2】若@EnableAutoConfiguration 等注解标有要exclude 的自动配置类,那么再将这个自动配置类排除掉; 【3】排除掉要exclude 的自动配置类后,然后再调用filter 方法进行进一步的过滤,再次排除一些不符合条件的自动配置类; 【4】经过重重过滤后,此时再触发AutoConfigurationImportEvent 事件,告诉ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类; 【5】 最后再将符合条件的自动配置类返回。
总结了AutoConfigurationEntry 方法主要的逻辑后,我们再来细看一下 AutoConfigurationImportSelector 的filter 方法:
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说明不匹配。
我们现在知道AutoConfigurationImportSelector 的filter 方法主要做了什么事情就行了,现在先不用研究的过深。
关于条件注解的讲解 :
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。 @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。 @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式的条件判断。 @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。 @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。 @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。 @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。 @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。 @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。 @ConditionalOnResource:当类路径下有指定的资源时触发实例化。 @ConditionalOnJndi:在JNDI存在的条件下触发实例化。 @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。 **有选择的导入自动配置类 **
this.group.selectImports 方法是如何进一步有选择的导入自动配置类的。直接看代码:
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 的自动配置类,然后再排序。
最后,我们再总结下SpringBoot自动配置的原理,主要做了以下事情:
从spring.factories配置文件中加载自动配置类; 加载的自动配置类中排除掉@EnableAutoConfiguration 注解的exclude 属性指定的自动配置类; 然后再用AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话)@ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果; 然后触发AutoConfigurationImportEvent 事件,告诉ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和exclude 的自动配置类。 最后spring再将最后筛选后的自动配置类导入IOC容器中。
以HttpEncodingAutoConfiguration ( Http 编码自动配置)为例解释自动配置原理:
@Configuration @EnableConfigurationProperties({HttpEncodingProperties.class}) @ConditionalOnWebApplication @ConditionalOnClass({CharacterEncodingFilter.class}) @ConditionalOnProperty( prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true ) public class HttpEncodingAutoConfiguration { private final HttpEncodingProperties properties; public HttpEncodingAutoConfiguration (HttpEncodingProperties properties) { this .properties = properties; } @Bean @ConditionalOnMissingBean({CharacterEncodingFilter.class}) public CharacterEncodingFilter characterEncodingFilter () { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this .properties.getCharset().name()); filter.setForceRequestEncoding(this .properties.shouldForce(Type.REQUEST)); filter.setForceResponseEncoding(this .properties.shouldForce(Type.RESPONSE)); return filter; } }
根据当前不同的条件判断,决定这个配置类是否生效。 一旦这个配置类生效,这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的 properties 类中获取的,这些类里面的每一个属性又是和配置文件绑定的。
# 我们能配置的属性都是来源于这个功能的properties类 spring.http.encoding.enabled=true spring.http.encoding.charset=utf-8 spring.http.encoding.force=true
所有在配置文件中能配置的属性都是在 xxxProperties 类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类。
@ConfigurationProperties(prefix = "spring.http.encoding") public class HttpEncodingProperties {
**精髓 **:
SpringBoot 启动会加载大量的自动配置类 我们看我们需要实现的功能有没有SpringBoot 默认写好的自动配置类 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们有我们要用的组件,我们就不需要再来配置了) 给容器中自动配置类添加组件的时候,会从properties 类中获取某些属性,我们就可以在配置文件中指定这些属性的值。 xxxAutoConfiguration :自动配置类,用于给容器中添加组件从而代替之前我们手动完成大量繁琐的配置。 xxxProperties : 封装了对应自动配置类的默认属性值,如果我们需要自定义属性值,只需要根据 xxxProperties 寻找相关属性在配置文件设值即可。 6. @ComponentScan 主要是从定义的扫描路径中,找出标识了需要装配的类自动装配到spring 的bean容器中。
常用属性如下 :
basePackages、value:指定扫描路径,如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径 basePackageClasses:指定具体扫描的类 includeFilters:指定满足Filter条件的类 excludeFilters:指定排除Filter条件的类 includeFilters和excludeFilters 的FilterType可选:ANNOTATION=注解类型 默认、ASSIGNABLE_TYPE(指定固定类)、ASPECTJ(ASPECTJ类型)、REGEX(正则表达式)、CUSTOM(自定义类型),自定义的Filter需要实现TypeFilter接口 @ComponentScan的配置如下:
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
借助excludeFilters将TypeExcludeFillter及FilterType这两个类进行排除 当前@ComponentScan注解没有标注basePackages及value,所以扫描路径默认为@ComponentScan注解的类所在的包为基本的扫描路径(也就是标注了@SpringBootApplication注解的项目启动类所在的路径)
抛出疑问 : @EnableAutoConfiguration注解是通过@Import注解加载了自动配置固定的bean @ComponentScan注解自动进行注解扫描
那么真正根据包扫描,把组件类生成实例对象存到IOC容器中,又是怎么来完成的?
2.3 源码分析-Run方法执行流程
1. SpringBoot项目的mian函数 @SpringBootApplication public class MyTestMVCApplication { public static void main (String[] args) { SpringApplication.run(MyTestMVCApplication.class, args); } } public static ConfigurableApplicationContext run (Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run (Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
2. SpringApplication() 构造方法 继续查看源码, SpringApplication 实例化过程,首先是进入带参数的构造方法,最终回来到两个参数的构造方法。
public SpringApplication (Class<?>... primarySources) { this (null , primarySources); } @SuppressWarnings({"unchecked", "rawtypes"}) public SpringApplication (ResourceLoader resourceLoader, Class<?>... primarySources) { this .resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null" ); this .primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this .webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class)); this .mainApplicationClass = deduceMainApplicationClass(); }
deduceWebApplicationType(); private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet" , "org.springframework.web.context.ConfigurableWebApplicationContext" }; private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet" ; private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler" ; private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer" ; private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext" ; private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext" ; static WebApplicationType deduceFromClasspath () { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null ) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null ) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null )) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null )) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; }
返回类型是WebApplicationType的枚举类型, WebApplicationType 有三个枚举,三个枚举的解释如
其中注释
具体的判断逻辑如下:
WebApplicationType.REACTIVE classpath下存在org.springframework.web.reactive.DispatcherHandler
WebApplicationType.SERVLET classpath下存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext
WebApplicationType.NONE 不满足以上条件。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); 初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer,实例化后存入内部属性
private <T> Collection<T> getSpringFactoriesInstances (Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<T> getSpringFactoriesInstances (Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } @SuppressWarnings("unchecked") private <T> List<T> createSpringFactoriesInstances (Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size()); for (String name : names) { try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); } } return instances; }
看看 getSpringFactoriesInstances 都干了什么,看源码,有一个方法很重要 loadFactoryNames()这个方法很重要,这个方法是spring-core中提供的从META-INF/spring.factories中获取指定的类(key)的同一入口方法。
在这里,获取的是key为 org.springframework.context.ApplicationContextInitializer 的类。
debug看看都获取到了哪些:
找到配置的ApplicationContextInitializer
类后对对其进行实例化
发现在上图所示的两个工程中找到了debug中看到的结果。
ApplicationContextInitializer 是Spring框架的类, 这个类的主要目的就是在ConfigurableApplicationContext 调用refresh()方法之前,回调这个类的initialize方法。通过 ConfigurableApplicationContext 的实例获取容器的环境Environment,从而实现对配置文件的修改完善等工作。
setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class)); 初始化classpath下 META-INF/spring.factories中已配置的 ApplicationListener。
ApplicationListener 的加载过程和上面的 ApplicationContextInitializer 类的加载过程是一样的。不多说了,至于 ApplicationListener 是spring的事件监听器,典型的观察者模式,通过ApplicationEvent 类和 ApplicationListener 接口,可以实现对spring容器全生命周期的监听,当然也可以自定义监听事件。
总结 关于 SpringApplication 类的构造过程,到这里我们就梳理完了。纵观 SpringApplication 类的实例化过程,我们可以看到,合理的利用该类,我们能在spring容器创建之前做一些预备工作,和定制化的需求。
比如,自定义SpringBoot的Banner,比如自定义事件监听器,再比如在容器refresh之前通过自定义ApplicationContextInitializer 修改配置一些配置或者获取指定的bean都是可以的。
3. run(args) 上一小节我们查看了SpringApplication 类的实例化过程,这一小节总结SpringBoot启动流程最重要的部分:run方法。 通过run方法梳理出SpringBoot启动的流程。 经过深入分析后,大家会发现SpringBoot也就是给Spring包了一层皮,事先替我们准备好Spring所需要的环境及一些基础。
public ConfigurableApplicationContext run (String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null ; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this .logStartupInfo) { new StartupInfoLogger(this .mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null ); throw new IllegalStateException(ex); } return context; }
在以上的代码中,启动过程中的重要步骤共分为六步
第一步:获取并启动监听器 第二步:构造应用上下文环境 第三步:初始化应用上下文 第四步:刷新应用上下文前的准备阶段 第五步:刷新应用上下文 第六步:刷新应用上下文后的扩展接口
第一步:获取并启动监听器 事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。Spring的事件为Bean和Bean之间的消息传递提供支持。当一个对象处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知,消息、邮件等情况。
private SpringApplicationRunListeners getRunListeners (String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this , args)); }
在这里面看到一个熟悉的方法:getSpringFactoriesInstances(),可以看下面的注释,前面已经详细介绍过该方法是怎么一步步的获取到META-INF/spring.factories中的指定的key的value,获取到以后怎么实例化类的。
private <T> Collection<T> getSpringFactoriesInstances (Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
回到run方法,debug这个代码 SpringApplicationRunListeners listeners = getRunListeners(args); 看一下获取的是哪个监听器:
EventPublishingRunListener监听器是Spring容器的启动监听器。listeners.starting(); 开启了监听事件
第二步:构造应用上下文环境 应用上下文环境包括什么呢?包括计算机的环境,Java环境,Spring的运行环境,Spring项目的配置(在SpringBoot中就是那个熟悉的application.properties/yml)等等。
private ConfigurableEnvironment prepareEnvironment (SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this .isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
方法中主要完成的工作,首先是创建并按照相应的应用类型配置相应的环境,然后根据用户的配置,配置系统环境,然后启动监听器,并加载系统配置文件。
ConfigurableEnvironment environment = getOrCreateEnvironment() private ConfigurableEnvironment getOrCreateEnvironment () { if (this .environment != null ) { return this .environment; } switch (this .webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default : return new StandardEnvironment(); } }
从上面的继承关系可以看出,StandardServletEnvironment是StandardEnvironment的子类。这两个对象也没什么好讲的,当是web项目的时候,环境上会多一些关于web环境的配置。
protected void configureEnvironment (ConfigurableEnvironment environment, String[] args) { if (this .addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } configurePropertySources(environment, args); configureProfiles(environment, args); }
如下图所示,在spring的启动参数中指定了参数:–spring.profiles.active=prod
在**configurePropertySources(environment, args)**
;中将args封装成了**SimpleCommandLinePropertySource**
并加入到了**environment**
中。
**configureProfiles(environment, args);**
根据启动参数激活了相应的配置文件。
listeners.environmentPrepared(environment); @Override public void multicastEvent (ApplicationEvent event) { multicastEvent(event, resolveDefaultEventType(event)); } @Override public void multicastEvent (final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); Executor executor = getTaskExecutor(); for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { if (executor != null ) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } }
查看getApplicationListeners(event, type)执行结果,发现一个重要的监听器ConfigFileApplicationListener。
这个监听器默认的从注释中标签所示的几个位置加载配置文件,并将其加入 上下文的 environment变量中。当然也可以通过配置指定。
debug跳过 listeners.environmentPrepared(environment); 这一行,查看environment属性,果真如上面所说的,配置文件的配置信息已经添加上来了。
第三步:初始化应用上下文 在SpringBoot工程中,应用类型分为三种,如下代码所示。
public enum WebApplicationType { NONE, SERVLET, REACTIVE; }
对应三种应用类型,SpringBoot项目有三种对应的应用上下文,我们以web工程为例,即其上下文为**AnnotationConfigServletWebServerApplicationContext**
。
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." + "annotation.AnnotationConfigApplicationContext" ; public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext" ; public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework." + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext" ; protected ConfigurableApplicationContext createApplicationContext () { Class<?> contextClass = this .applicationContextClass; if (contextClass == null ) { try { switch (this .webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break ; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break ; default : contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, please specify an ApplicationContextClass" , ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
看一下AnnotationConfigServletWebServerApplicationContext的设计:
应用上下文可以理解成IoC容器的高级表现形式,应用上下文确实是在IoC容器的基础上丰富了一些高级功能。
应用上下文对IoC容器是持有的关系。他的一个属性beanFactory就是IoC容器(DefaultListableBeanFactory)。所以他们之间是持有,和扩展的关系。
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { private final DefaultListableBeanFactory beanFactory; @Nullable private ResourceLoader resourceLoader; public GenericApplicationContext () { this .beanFactory = new DefaultListableBeanFactory(); } }
beanFactory正是在AnnotationConfigServletWebServerApplicationContext
实现的接口GenericApplicationContext
中定义的。在上面createApplicationContext()
方法中的,BeanUtils.instantiateClass(contextClass)
这个方法中,不但初始化了AnnotationConfigServletWebServerApplicationContext
类,也就是我们的上下文context,同样也触发了GenericApplicationContext
类的构造函数,从而IoC容器也创建了。
仔细看他的构造函数,有没有发现一个很熟悉的类DefaultListableBeanFactory
,没错,DefaultListableBeanFactory就是IoC容器真实面目了。在后面的refresh()方法分析中,DefaultListableBeanFactory是无处不在的存在感。
如上图所示,context就是我们熟悉的上下文(也有人称之为容器,都可以,看个人爱好和理解),beanFactory就是我们所说的IoC容器的真实面孔了。细细感受下上下文和容器的联系和区别,对于我们理解源码有很大的帮助。在我们学习过程中,我们也是将上下文和容器严格区分开来的。
第四步:刷新应用上下文前的准备阶段 prepareContext()方法 private void prepareContext (ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); if (this .logStartupInfo) { logStartupInfo(context.getParent() == null ); logStartupProfileInfo(context); } ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments" , applicationArguments); if (printedBanner != null ) { beanFactory.registerSingleton("springBootBanner" , printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this .allowBeanDefinitionOverriding); } if (this .lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty" ); load(context, sources.toArray(new Object[0 ])); listeners.contextLoaded(context); }
首先看这行 Set sources = getAllSources(); 在getAllSources()中拿到了我们的启动类。重点在这行 load(context, sources.toArray(new Object[0])); ,其他的方法请参阅注释。
跟进load()方法,看源码:
protected void load (ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources); if (this .beanNameGenerator != null ) { loader.setBeanNameGenerator(this .beanNameGenerator); } if (this .resourceLoader != null ) { loader.setResourceLoader(this .resourceLoader); } if (this .environment != null ) { loader.setEnvironment(this .environment); } loader.load(); }
getBeanDefinitionRegistry() 继续看getBeanDefinitionRegistry()方法的源码:
private BeanDefinitionRegistry getBeanDefinitionRegistry (ApplicationContext context) { if (context instanceof BeanDefinitionRegistry) { return (BeanDefinitionRegistry) context; } if (context instanceof AbstractApplicationContext) { return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory(); } throw new IllegalStateException("Could not locate BeanDefinitionRegistry" ); }
这里将我们前文创建的上下文强转为BeanDefinitionRegistry,他们之间是有继承关系的。BeanDefinitionRegistry定义了很重要的方法registerBeanDefinition(),该方法将BeanDefinition注册进DefaultListableBeanFactory容器的beanDefinitionMap中。
createBeanDefinitionLoader() 继续看createBeanDefinitionLoader()方法,最终进入了BeanDefinitionLoader类的构造方法,如下:
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) { Assert.notNull(registry, "Registry must not be null" ); Assert.notEmpty(sources, "Sources must not be empty" ); this .sources = sources; this .annotatedReader = new AnnotatedBeanDefinitionReader(registry); this .xmlReader = new XmlBeanDefinitionReader(registry); if (isGroovyPresent()) { this .groovyReader = new GroovyBeanDefinitionReader(registry); } this .scanner = new ClassPathBeanDefinitionScanner(registry); this .scanner.addExcludeFilter(new ClassExcludeFilter(sources)); }
先记住上面的三个属性,上面三个属性在,BeanDefinition的Resource定位,和BeanDefinition的注册中起到了很重要的作用。
loader.load(); private int load (Object source) { Assert.notNull(source, "Source must not be null" ); if (source instanceof Class<?>) { return load((Class<?>) source); } if (source instanceof Resource) { return load((Resource) source); } if (source instanceof Package) { return load((Package) source); } if (source instanceof CharSequence) { return load((CharSequence) source); } throw new IllegalArgumentException("Invalid source type " + source.getClass()); } private int load (Class<?> source) { if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); load(loader); } if (isComponent(source)) { this .annotatedReader.register(source); return 1 ; } return 0 ; }
当前我们的主类会按Class加载
isComponent(source)判断主类是不是存在@Component注解,主类@SpringBootApplication是一个组合注解,包含@Component。 this.annotatedReader.register(source);跟进register()方法,最终进到AnnotatedBeanDefinitionReader类的doRegisterBean()方法。
private <T> void doRegisterBean (Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); if (this .conditionEvaluator.shouldSkip(abd.getMetadata())) { return ; } abd.setInstanceSupplier(supplier); ScopeMetadata scopeMetadata = this .scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); String beanName = (name != null ? name : this .beanNameGenerator.generateBeanName(abd, this .registry)); AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); if (qualifiers != null ) { for (Class<? extends Annotation> qualifier : qualifiers) { if (Primary.class == qualifier) { abd.setPrimary(true ); } else if (Lazy.class == qualifier) { abd.setLazyInit(true ); } else { abd.addQualifier(new AutowireCandidateQualifier(qualifier)); } } } if (customizers != null ) { for (BeanDefinitionCustomizer customizer : customizers) { customizer.customize(abd); } } BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this .registry); BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this .registry); }
在该方法中将主类封装成AnnotatedGenericBeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);方法将BeanDefinition注册进beanDefinitionMap
public static void registerBeanDefinition ( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); String[] aliases = definitionHolder.getAliases(); if (aliases != null ) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } } @Override public void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty" ); Assert.notNull(beanDefinition, "BeanDefinition must not be null" ); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed" , ex); } } BeanDefinition existingDefinition = this .beanDefinitionMap.get(beanName); if (existingDefinition != null ) { if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } else if (existingDefinition.getRole() < beanDefinition.getRole()) { if (logger.isInfoEnabled()) { logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]" ); } } else if (!beanDefinition.equals(existingDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]" ); } } else { if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]" ); } } this .beanDefinitionMap.put(beanName, beanDefinition); } else { if (hasBeanCreationStarted()) { synchronized (this .beanDefinitionMap) { this .beanDefinitionMap.put(beanName, beanDefinition); List<String> updatedDefinitions = new ArrayList<>(this .beanDefinitionNames.size() + 1 ); updatedDefinitions.addAll(this .beanDefinitionNames); updatedDefinitions.add(beanName); this .beanDefinitionNames = updatedDefinitions; removeManualSingletonName(beanName); } } else { this .beanDefinitionMap.put(beanName, beanDefinition); this .beanDefinitionNames.add(beanName); removeManualSingletonName(beanName); } this .frozenBeanDefinitionNames = null ; } if (existingDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); } else if (isConfigurationFrozen()) { clearByTypeCache(); } }
最终来到DefaultListableBeanFactory类的registerBeanDefinition()方法,DefaultListableBeanFactory类还熟悉吗?相信大家一定非常熟悉这个类了。
DefaultListableBeanFactory是IoC容器的具体产品。
仔细看这个方法registerBeanDefinition(),首先会检查是否已经存在,如果存在并且不允许被覆盖则直接抛出异常。不存在的话就直接注册进beanDefinitionMap中。
debug跳过prepareContext()方法,可以看到,启动类的BeanDefinition已经注册进来了。
第五步:刷新应用上下文(IOC容器的初始化过程) IOC容器的初始化过程,主要分下面三步
BeanDefinition的Resource定位 BeanDefinition的载入 向IoC容器注册BeanDefinition 主要从refresh()方法中总结IoC容器的初始化过程。
从run方法的,refreshContext()方法一路跟下去,最终来到AbstractApplicationContext类的refresh()方法。
@Override public void refresh () throws BeansException, IllegalStateException { synchronized (this .startupShutdownMonitor) { prepareRefresh(); ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { postProcessBeanFactory(beanFactory); invokeBeanFactoryPostProcessors(beanFactory); /注册Bean的后处理器,在Bean创建过程中调用 registerBeanPostProcessors(beanFactory); initMessageSource(); initApplicationEventMulticaster(); onRefresh(); registerListeners(); finishBeanFactoryInitialization(beanFactory); finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } destroyBeans(); cancelRefresh(ex); throw ex; } finally { resetCommonCaches(); } } }
obtainFreshBeanFactory(); 在启动流程的第三步:初始化应用上下文。中我们创建了应用的上下文,并触发了GenericApplicationContext类的构造方法如下所示,创建了beanFactory,也就是创建了DefaultListableBeanFactory类。
public GenericApplicationContext () { this .beanFactory = new DefaultListableBeanFactory(); } protected ConfigurableListableBeanFactory obtainFreshBeanFactory () { refreshBeanFactory(); return getBeanFactory(); }
从上面代码可知,在该方法中主要做了三个工作,刷新beanFactory,获取beanFactory,返回beanFactory。
首先看一下refreshBeanFactory()方法,跟下去来到GenericApplicationContext类的refreshBeanFactory()发现也没做什么。
@Override protected final void refreshBeanFactory () throws IllegalStateException { if (!this .refreshed.compareAndSet(false , true )) { throw new IllegalStateException( "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once" ); } this .beanFactory.setSerializationId(getId()); }
TIPS: 1,AbstractApplicationContext类有两个子类实现了refreshBeanFactory(),但是在前面第三步初始化上下文的时候,实例化了GenericApplicationContext类,所以没有进入AbstractRefreshableApplicationContext中的refreshBeanFactory()方法。 2,this.refreshed.compareAndSet(false, true)这行代码在这里表示:GenericApplicationContext只允许刷新一次这行代码,很重要,不是在Spring中很重要,而是这行代码本身。首先看一下this.refreshed 属性: private final AtomicBoolean refreshed = new AtomicBoolean(); java J.U.C并发包中很重要的一个原子类AtomicBoolean。通过该类的compareAndSet()方法 可以实现一段代码绝对只实现一次的功能。
prepareBeanFactory(beanFactory) 准备BeanFactory
postProcessBeanFactory(beanFactory); postProcessBeanFactory()方法向上下文中添加了一系列的Bean的后置处理器。后置处理器工作的时机是在所有的beanDenifition加载完成之后,bean实例化之前执行。简单来说Bean的后置处理器可以修改BeanDefinition的属性信息。
invokeBeanFactoryPostProcessors(beanFactory);(重点) IoC容器的初始化过程包括三个步骤,在invokeBeanFactoryPostProcessors()方法中完成了IoC容器初始化过程的三个步骤
第一步:Resource定位 在SpringBoot中,我们都知道他的包扫描是从主类所在的包开始扫描的,prepareContext()方法中,会先将主类解析成BeanDefinition,然后在refresh()方法的invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition获取basePackage的路径。这样就完成了定位的过程。其次SpringBoot的各种starter是通过SPI扩展机制实现的自动装配,SpringBoot的自动装配同样也是在invokeBeanFactoryPostProcessors()方法中实现的。还有一种情况,在SpringBoot中有很多的@EnableXXX注解,细心点进去看的应该就知道其底层是@Import注解,在invokeBeanFactoryPostProcessors()方法中也实现了对该注解指定的配置类的定位加载
常规的在SpringBoot中有三种实现定位,第一个是主类所在包的,第二个是SPI扩展机制实现的自动装配(比如各种starter),第三种就是@Import注解指定的类。(对于非常规的不说了)
第二步:BeanDefinition的载入 在第一步中说了三种Resource的定位情况,定位后紧接着就是BeanDefinition的分别载入。所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成:
classpath:com/lagou/**/.class这样的形式,然后一个叫做xPathMatchingResourcePatternResolver的类会将该路径下所有的.class文件都加载进来,然后遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition。大致过程就是这样的了。
TIPS: @Configuration,@Controller,@Service等注解底层都是@Component注解,只不过包装了一层罢了。
第三个过程:注册BeanDefinition 这个过程通过调用上文提到的BeanDefinitionRegister接口的实现来完成。这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。通过上文的分析,我们可以看到,在IoC容器中将BeanDefinition注入到一个ConcurrentHashMap中,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。比如DefaultListableBeanFactory 中的beanDefinitionMap属性。
最终来到ConfigurationClassParser中的parse(Set<BeanDefinitionHolder> configCandidates)
方法
public void parse (Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]" , ex); } } this .deferredImportSelectorHandler.process(); }
protected final void parse (AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER); } protected void processConfigurationClass (ConfigurationClass configClass, Predicate<String> filter) throws IOException { if (this .conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return ; } ConfigurationClass existingClass = this .configurationClasses.get(configClass); if (existingClass != null ) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } return ; } else { this .configurationClasses.remove(configClass); this .knownSuperclasses.values().removeIf(configClass::equals); } } SourceClass sourceClass = asSourceClass(configClass, filter); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass != null ); this .configurationClasses.put(configClass, configClass); } @Nullable protected final SourceClass doProcessConfigurationClass ( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { if (configClass.getMetadata().isAnnotated(Component.class.getName())) { processMemberClasses(configClass, sourceClass, filter); } for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this .environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment" ); } } Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this .conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { Set<BeanDefinitionHolder> scannedBeanDefinitions = this .componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null ) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this .metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } processImports(configClass, sourceClass, getImports(sourceClass), filter, true ); AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null ) { String[] resources = importResource.getStringArray("locations" ); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader" ); for (String resource : resources) { String resolvedResource = this .environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } processInterfaces(configClass, sourceClass); if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java" ) && !this .knownSuperclasses.containsKey(superclass)) { this .knownSuperclasses.put(superclass, configClass); return sourceClass.getSuperClass(); } } return null ; }
扫描标注了@ComponentScan 注解,读取注解的内容
TIPS: 在以上代码的parse(bdCand.getBeanClassName(), holder.getBeanName());会进行递归调用, 因为当Spring扫描到需要加载的类会进一步判断每一个类是否满足是@Component/@Configuration注解的类,如果满足会递归调用parse()方法,查找其相关的类。 同样的processImports(configClass, sourceClass, getImports(sourceClass),true); 通过@Import注解查找到的类同样也会递归查找其相关的类。 两个递归在debug的时候会很乱,用文字叙述起来更让人难以理解,所以,我们只关注对主类的解析,及其类的扫描过程。
上面代码中 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(… 获取主类上的@PropertySource注解),解析该注解并将该注解指定的properties配置文件中的值存储到Spring的 Environment中, Environment接口提供方法去读取配置文件中的值,参数是properties文件中定义的key值。 Set componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); 解析主类上的@ComponentScan注解,后面的代码将会解析该注解并进行包扫描。 processImports(configClass, sourceClass, getImports(sourceClass), true); 解析主类上的@Import注解,并加载该注解指定的配置类。
TIPS: 在spring中好多注解都是一层一层封装的,比如@EnableXXX,是对@Import注解的二次封装。 @SpringBootApplication注解=@ComponentScan+@EnableAutoConfiguration+@Import+@Configuration+@Component。 @Controller,@Service等等是对@Component的二次封装。
org.springframework.context.annotation.ComponentScanAnnotationParser#parse
解析@ComponentScan标注的注解内容
public Set<BeanDefinitionHolder> parse (AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this .registry, componentScan.getBoolean("useDefaultFilters" ), this .environment, this .resourceLoader); ... if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false , false ) { @Override protected boolean matchClassName (String className) { return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); }
TIPS: 为什么只有一个还要用一个集合呢,因为我们也可以用@ComponentScan注解指定扫描路径。
protected Set<BeanDefinitionHolder> doScan (String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified" ); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this .scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this .beanNameGenerator.generateBeanName(candidate, this .registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this .registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this .registry); } } } return beanDefinitions; } public Set<BeanDefinition> findCandidateComponents (String basePackage) { if (this .componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this .componentsIndex, basePackage); } else { return scanCandidateComponents(basePackage); } } private Set<BeanDefinition> scanCandidateComponents (String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this .resourcePattern; Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } else { if (debugEnabled) { logger.debug("Ignored because not a concrete top-level class: " + resource); } } } else { if (traceEnabled) { logger.trace("Ignored because not matching any filter: " + resource); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate component class: " + resource, ex); } } else { if (traceEnabled) { logger.trace("Ignored because not readable: " + resource); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning" , ex); } return candidates; }
上面的三个方法经过层级调用,doScan方法第7行 Set candidates =findCandidateComponents(basePackage); 从basePackage中扫描类并解析成BeanDefinition,拿到所有符合条件的类后在第24行 registerBeanDefinition(definitionHolder, this.registry); 将该类注册进IoC容器。也就是说在这个方法中完成了IoC容器初始化过程的第二三步,BeanDefinition的载入,和BeanDefinition的注册。
scanCandidateComponents行将basePackage拼接成classpath:org/springframework/boot/demo/**/.class,在第16行的getResources(packageSearchPath);方法中扫描到了该路径下的所有的类。然后遍历这些
Resources,isCandidateComponent判断该类是不是 @Component 注解标注的类,并且不是需要排除掉的类。在第29行将扫描到的类,解析成ScannedGenericBeanDefinition,该类是BeanDefinition接口的实现类。OK,IoC容器的BeanDefinition载入到这里就结束了。
回到前面的doScan()方法,debug看一下结果(截图中所示的就是定位的需要交给Spring容器管理的类。
registerBeanDefinition(definitionHolder, this.registry); 查看registerBeanDefinition()方法。是不是有点眼熟,在前面介绍prepareContext()方法时,我们详细介绍了主类的BeanDefinition是怎么一步一步的注册进DefaultListableBeanFactory的beanDefinitionMap中的。完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。此时,在使用的IoC容器DefaultListableFactory中已经建立了整个Bean的配置信息,而这些BeanDefinition已经可以被容器使用了。他们都在BeanbefinitionMap里被检索和使用。容器的作用就是对这些信息进行处理和维护。这些信息是容器简历依赖反转的基础。
OK,到这里IoC容器的初始化过程的三个步骤就梳理完了。当然这只是针对SpringBoot的包扫描的定位方式的BeanDefinition的定位,加载,和注册过程。前面我们说过,还有两种方式@Import和SPI扩展实现的starter的自动装配。
@Import注解的解析过程 现在大家也应该知道了,各种@EnableXXX注解,很大一部分都是对@Import的二次封装(其实也是为了解耦,比如当@Import导入的类发生变化时,我们的业务系统也不需要改任何代码)。我们又要回到上文中的ConfigurationClassParser类的doProcessConfigurationClass方法的processImports(configClass, sourceClass, getImports(sourceClass), true);,跳跃性比较大。上面解释过,我们只针对主类进行分析,因为这里有递归。processImports(configClass, sourceClass, getImports(sourceClass), true);中configClass和sourceClass参数都是主类相对应的。首先看getImports(sourceClass);
private Set<SourceClass> getImports (SourceClass sourceClass) throws IOException { Set<SourceClass> imports = new LinkedHashSet<>(); Set<SourceClass> visited = new LinkedHashSet<>(); collectImports(sourceClass, imports, visited); return imports; }
主类上的@SpringBootApplication中的@Import注解指定的类记下来再回到ConfigurationClassParser类的parse(Set configCandidates):
public void parse (Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]" , ex); } } this .deferredImportSelectorHandler.process(); }
点进process方法:
public void process () { List<DeferredImportSelectorHolder> deferredImports = this .deferredImportSelectors; this .deferredImportSelectors = null ; try { if (deferredImports != null ) { DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler(); deferredImports.sort(DEFERRED_IMPORT_COMPARATOR); deferredImports.forEach(handler::register); handler.processGroupImports(); } } finally { this .deferredImportSelectors = new ArrayList<>(); } }
继续点击handler.processGroupImports();
public void processGroupImports () { for (DeferredImportSelectorGrouping grouping : this .groupings.values()) { Predicate<String> exclusionFilter = grouping.getCandidateFilter(); grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this .configurationClasses.get(entry.getMetadata()); try { processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter), Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)), exclusionFilter, false ); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]" , ex); } }); } } public Iterable<Group.Entry> getImports() { for (DeferredImportSelectorHolder deferredImport : this .deferredImports) { this .group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } return this .group.selectImports(); }
再次进入org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process
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); } }
第六步:刷新应用上下文后的扩展接口 protected void afterRefresh (ConfigurableApplicationContext context, ApplicationArguments args) {}
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
参考SpirngBoot启动过程分析 · 语雀 (yuque.com)