准备
SpringBoot 为我们做的自动配置, 确实方便快捷, 若不大明白 SpringBoot 内部启动原理, 以后难免会吃亏, 所以这次博主就跟你们一起一步步揭开 SpringBoot 的神秘面纱, 让它不再神秘.
旅程开始
开发任何一个 SpringBoot 项目, 都会用到如下启动类:
- @SpringBootApplication
- public class Application { public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
- }
从上面代码可以看出, Annotation 定义 (@SpringBootApplication) 和类定义 (SpringApplication.run) 最为耀眼, 所以要揭开 SpringBoot 的神秘面纱, 我们要从这两位开始就可以了.
从 @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 {
- ...
- }
虽然定义使用了多个 Annotation 进行了原信息标注, 但实际上重要的只有三个 Annotation:
- @Configuration(@SpringBootConfiguration 点开查看发现里面还是应用了 @Configuration)
- @EnableAutoConfiguration
- @ComponentScan
所以, 如果我们使用如下的 SpringBoot 启动类, 整个 SpringBoot 应用依然可以与之前的启动类功能对等:
- @Configuration
- @EnableAutoConfiguration
- @ComponentScan
- public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
- }
每次写这 3 个比较累, 所以写一个 @SpringBootApplication 方便点. 接下来分别介绍这 3 个 Annotation.
@Configuration
这里的 @Configuration 对我们来说不陌生, 它就是 JavaConfig 形式的 Spring Ioc 容器的配置类使用的那个 @Configuration,SpringBoot 社区推荐使用基于 JavaConfig 的配置形式, 所以, 这里的启动类标注了 @Configuration 之后, 本身其实也是一个 IoC 容器的配置类.
举几个简单例子回顾下, XML 跟 config 配置方式的区别:
表达形式层面
基于 XML 配置的方式是这样:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
- default-lazy-init="true">
- <!--bean 定义 -->
- </beans>
而基于 JavaConfig 的配置方式是这样:
- @Configuration
- public class MockConfiguration{
- //bean 定义
- }
任何一个标注了 @Configuration 的 Java 类定义都是一个 JavaConfig 配置类.
注册 bean 定义层面
基于 XML 的配置形式是这样:
- <bean id="mockService" class="..MockServiceImpl">
- ...
- </bean>
而基于 JavaConfig 的配置形式是这样的:
- @Configuration
- public class MockConfiguration{
- @Bean
- public MockService mockService(){
- return new MockServiceImpl();
- }
- }
任何一个标注了 @Bean 的方法, 其返回值将作为一个 bean 定义注册到 Spring 的 IoC 容器, 方法名将默认成该 bean 定义的 id.
表达依赖注入关系层面
为了表达 bean 与 bean 之间的依赖关系, 在 XML 形式中一般是这样:
- <bean id="mockService" class="..MockServiceImpl">
- <propery name ="dependencyService" ref="dependencyService" />
- </bean>
- <bean id="dependencyService" class="DependencyServiceImpl"></bean>
而基于 JavaConfig 的配置形式是这样的:
- @Configuration
- public class MockConfiguration{
- @Bean
- public MockService mockService(){
- return new MockServiceImpl(dependencyService());
- }
- @Bean
- public DependencyService dependencyService(){
- return new DependencyServiceImpl();
- }
- }
如果一个 bean 的定义依赖其他 bean, 则直接调用对应的 JavaConfig 类中依赖 bean 的创建方法就可以了.
@ComponentScan
@ComponentScan 这个注解在 Spring 中很重要, 它对应 XML 配置中的元素,@ComponentScan 的功能其实就是自动扫描并加载符合条件的组件 (比如 @Component 和 @Repository 等) 或者 bean 定义, 最终将这些 bean 定义加载到 IoC 容器中.
我们可以通过 basePackages 等属性来细粒度的定制 @ComponentScan 自动扫描的范围, 如果不指定, 则默认 Spring 框架实现会从声明 @ComponentScan 所在类的 package 进行扫描.
注: 所以 SpringBoot 的启动类最好是放在 root package 下, 因为默认不指定 basePackages.
@EnableAutoConfiguration
个人感觉 @EnableAutoConfiguration 这个 Annotation 最为重要, 所以放在最后来解读, 大家是否还记得 Spring 框架提供的各种名字为 @Enable 开头的 Annotation 定义? 比如 @EnableScheduling,@EnableCaching,@EnableMBeanExport 等,@EnableAutoConfiguration 的理念和做事方式其实一脉相承, 简单概括一下就是, 借助 @Import 的支持, 收集和注册特定场景相关的 bean 定义.
@EnableScheduling 是通过 @Import 将 Spring 调度框架相关的 bean 定义都加载到 IoC 容器.
@EnableMBeanExport 是通过 @Import 将 JMX 相关的 bean 定义加载到 IoC 容器.
而 @EnableAutoConfiguration 也是借助 @Import 的帮助, 将所有符合自动配置条件的 bean 定义加载到 IoC 容器, 仅此而已!
@EnableAutoConfiguration 作为一个复合 Annotation, 其自身定义关键信息如下:
- @SuppressWarnings("deprecation")
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Inherited
- @AutoConfigurationPackage
- @Import(EnableAutoConfigurationImportSelector.class)
- public @interface EnableAutoConfiguration {
- ...
- }
其中, 最关键的要属 @Import(EnableAutoConfigurationImportSelector.class), 借助 EnableAutoConfigurationImportSelector,@EnableAutoConfiguration 可以帮助 SpringBoot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IoC 容器. 就像一只 "八爪鱼" 一样
借助于 Spring 框架原有的一个工具类: SpringFactoriesLoader 的支持,@EnableAutoConfiguration 可以智能的自动配置功效才得以大功告成!
自动配置幕后英雄: SpringFactoriesLoader 详解
SpringFactoriesLoader 属于 Spring 框架私有的一种扩展方案, 其主要功能就是从指定的配置文件 META-INF/spring.factories 加载配置.
- public abstract class SpringFactoriesLoader {
- //...
- public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
- ...
- }
- public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
- ....
- }
- }
配合 @EnableAutoConfiguration 使用的话, 它更多是提供一种配置查找的功能支持, 即根据 @EnableAutoConfiguration 的完整类名 org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为查找的 Key, 获取对应的一组 @Configuration 类
上图就是从 SpringBoot 的 autoconfigure 依赖包中的 META-INF/spring.factories 配置文件中摘录的一段内容, 可以很好地说明问题.
所以,@EnableAutoConfiguration 自动配置的魔法骑士就变成了: 从 classpath 中搜寻所有的 META-INF/spring.factories 配置文件, 并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射 (Java Refletion) 实例化为对应的标注了 @Configuration 的 JavaConfig 形式的 IoC 容器配置类, 然后汇总为一个并加载到 IoC 容器.
深入探索 SpringApplication 执行流程
SpringApplication 的 run 方法的实现是我们本次旅程的主要线路, 该方法的主要流程大体可以归纳如下:
1) 如果我们使用的是 SpringApplication 的静态 run 方法, 那么, 这个方法里面首先要创建一个 SpringApplication 对象实例, 然后调用这个创建好的 SpringApplication 的实例方法. 在 SpringApplication 实例初始化的时候, 它会提前做几件事情:
根据 classpath 里面是否存在某个特征类 (org.springframework.web.context.ConfigurableWebApplicationContext) 来决定是否应该创建一个为 Web 应用使用的 ApplicationContext 类型.
使用 SpringFactoriesLoader 在应用的 classpath 中查找并加载所有可用的 ApplicationContextInitializer.
使用 SpringFactoriesLoader 在应用的 classpath 中查找并加载所有可用的 ApplicationListener.
推断并设置 main 方法的定义类.
2) SpringApplication 实例初始化完成并且完成设置后, 就开始执行 run 方法的逻辑了, 方法执行伊始, 首先遍历执行所有通过 SpringFactoriesLoader 可以查找到并加载的 SpringApplicationRunListener. 调用它们的 started()方法, 告诉这些 SpringApplicationRunListener,"嘿, SpringBoot 应用要开始执行咯!".
3) 创建并配置当前 Spring Boot 应用将要使用的 Environment(包括配置要使用的 PropertySource 以及 Profile).
4) 遍历调用所有 SpringApplicationRunListener 的 environmentPrepared()的方法, 告诉他们:"当前 SpringBoot 应用使用的 Environment 准备好了咯!".
5) 如果 SpringApplication 的 showBanner 属性被设置为 true, 则打印 banner.
6) 根据用户是否明确设置了 applicationContextClass 类型以及初始化阶段的推断结果, 决定该为当前 SpringBoot 应用创建什么类型的 ApplicationContext 并创建完成, 然后根据条件决定是否添加 ShutdownHook, 决定是否使用自定义的 BeanNameGenerator, 决定是否使用自定义的 ResourceLoader, 当然, 最重要的, 将之前准备好的 Environment 设置给创建好的 ApplicationContext 使用.
7) ApplicationContext 创建好之后, SpringApplication 会再次借助 Spring-FactoriesLoader, 查找并加载 classpath 中所有可用的 ApplicationContext-Initializer, 然后遍历调用这些 ApplicationContextInitializer 的 initialize(applicationContext)方法来对已经创建好的 ApplicationContext 进行进一步的处理.
8) 遍历调用所有 SpringApplicationRunListener 的 contextPrepared()方法.
9) 最核心的一步, 将之前通过 @EnableAutoConfiguration 获取的所有配置以及其他形式的 IoC 容器配置加载到已经准备完毕的 ApplicationContext.
10) 遍历调用所有 SpringApplicationRunListener 的 contextLoaded()方法.
11) 调用 ApplicationContext 的 refresh()方法, 完成 IoC 容器可用的最后一道工序.
12) 查找当前 ApplicationContext 中是否注册有 CommandLineRunner, 如果有, 则遍历执行它们.
13) 正常情况下, 遍历执行 SpringApplicationRunListener 的 finished()方法,(如果整个过程出现异常, 则依然调用所有 SpringApplicationRunListener 的 finished()方法, 只不过这种情况下会将异常信息一并传入处理)
去除事件通知点后, 整个流程如下:
旅程结束
到此, SpringBoot 的核心组件完成了基本的解析, 综合来看, 大部分都是 Spring 框架背后的一些概念和实践方式, SpringBoot 只是在这些概念和实践上对特定的场景事先进行了固化和升华, 而也恰恰是这些固化让我们开发基于 Sping 框架的应用更加方便高效.
来源: http://www.bubuko.com/infodetail-2766434.html