转载自:
http://majunwei.com/view/201708231840127244.html
https://www.cnblogs.com/itrena/p/8352846.html
前言
我们知道,如果不需要特殊的配置,只需要在main方法里调用SpringApplicatio.run()方法即可启动Spring Boot应用:
1 | @SpringBootApplication |
关注两点:
1. 容器启动
1. @SpringBootApplication注解
1 | @Target(ElementType.TYPE) |
@SpringBootConfiguration(实际就是个@Configuration):表示这是一个JavaConfig配置类,可以在这个类中自定义bean,依赖关系等。-》这个是spring-boot特有的注解,常用到。
@EnableAutoConfiguration:借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器(建议放在根包路径下,这样可以扫描子包和类)。
@ComponentScan:spring的自动扫描注解,可定义扫描范围,加载到IOC容器。
@EnableAutoConfiguration这个注解的源码
1 | @SuppressWarnings("deprecation") |
核心是一个EnableAutoConfigurationImportSelector类图如下:
核心方法在顶级接口ImportSelector的selectImports(),源码如下:
1 | @Override |
3个核心方法:
1)loadMetadata 加载配置
其实就是用类加载器去加载:META-INF/spring-autoconfigure-metadata.properties(spring-boot-autoconfigure-1.5.9.RELEASE-sources.jar)文件中定义的配置,返回PropertiesAutoConfigurationMetadata(实现了AutoConfigurationMetadata接口,封装了属性的get set方法)
2)getCandidateConfigurations获取默认支持的自动配置类名列表
自动配置灵魂方法,SpringFactoriesLoader.loadFactoryNames 从META-INF/spring.factories(spring-boot-autoconfigure-1.5.9.RELEASE-sources.jar)文件中获取自动配置类key=EnableAutoConfiguration.class的配置。
1 | protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, |
实际获取了什么?spring.factories文件如下,实际获取了# Auto Configure自动配置模块的所有类。
1 | # Initializers |
3)filter过滤器 根据OnClassCondition注解把不满足条件的过滤掉
1 | private List<String> filter(List<String> configurations, |
SpringApplication启动流程
SpringApplication的启动过程非常复杂,下面是在调用SpringApplication.run方法之后启动的关键动作:
既然要了解SpringApplication的启动流程,第一步当然是进入源码里看看喽:
1 | public ConfigurableApplicationContext run(String... args) { |
1. 初始化监听器
这里会初始化Spring Boot自带的监听器,以及添加到SpringApplication的自定义监听器。
实际是SpringApplicationRunListener类
1 | private SpringApplicationRunListeners getRunListeners(String[] args) { |
2. 发布ApplicationStartedEvent事件
到这一步,Spring Boot会发布一个ApplicationStartedEvent事件。如果你想在这个时候执行一些代码可以通过实现ApplicationListener接口实现;
下面是ApplicationListener接口的定义,注意这里有个
1 | public interface ApplicationListener<E extends ApplicationEvent> extends EventListener |
例如,你想监听ApplicationStartedEvent事件,你可以这样定义:
1 | public class ApplicationStartedListener implements ApplicationListener<ApplicationStartedEvent> |
然后通过SpringApplication.addListener(..)添加进去即可。
3. 装配参数和环境
在这一步,首先会初始化参数,然后装配环境,确定是web环境还是非web环境。
4. 发布ApplicationEnvironmentPreparedEvent事件
准确的说,这个应该属于第三步,在装配完环境后,就触发ApplicationEnvironmentPreparedEvent事件。如果想在这个时候执行一些代码,可以订阅这个事件的监听器,方法同第二步。
5. 打印Banner
看过Spring Boot实例教程 - 自定义Banner的同学会很熟悉,启动的Banner就是在这一步打印出来的。
6. 创建ApplicationContext
这里会根据是否是web环境,来决定创建什么类型的ApplicationContext,ApplicationContext不要多说了吧,不知道ApplicationContext是啥的同学,出门左转补下Spring基础知识吧。
7. 装配Context
这里会设置Context的环境变量、注册Initializers、beanNameGenerator等。
8. 发布ApplicationPreparedEvent事件
这里放在第七步会更准确,因为这个是在装配Context的时候发布的。
值得注意的是:这里是假的,假的,假的,源码中是空的,并没有真正发布ApplicationPreparedEvent事件。不知道作者这么想的???
9. 注册、加载等
注册springApplicationArguments、springBootBanner,加载资源等。
10. 发布ApplicationPreparedEvent事件
注意,到这里才是真正发布了ApplicationPreparedEvent事件。这里和第八步好让人误解。
11. refreshContext
装配context beanfactory等非常重要的核心组件。
12. afterRefreshContext
这里会调用自定义的Runners,不知道Runners是什么的同学,请参考Spring Boot官方文档 - SpringApplication
13. 发布ApplicationReadyEvent事件
最后一步,发布ApplicationReadyEvent事件,启动完毕,表示服务已经可以开始正常提供服务了。通常我们这里会监听这个事件来打印一些监控性质的日志,表示应用正常启动了。添加方法同第二步。
注意:如果启动失败,这一步会发布ApplicationFailedEvent事件。
到这里,Spring Boot启动的一些关键动作就介绍完了。
2. 总结
按照前面的分析,Spring-boot容器启动流程总体可划分为2部分:
1)执行注解:扫描指定范围下的bean、载入自动配置类对应的bean加载到IOC容器。
2)man方法中具体SpringAppliocation.run(),全流程贯穿SpringApplicationEvent,有6个子类:
ApplicationFailedEvent.class
ApplicationPreparedEvent.class
ApplicationReadyEvent.class
ApplicationStartedEvent.class
ApplicationStartingEvent.class
SpringApplicationEvent.class
这里用到了很经典的spring事件驱动模型,飞机票:Spring事件驱动模型和观察者模式
类图如下:
如上图,就是一个经典spring 事件驱动模型,包含3种角色:事件发布者、事件、监听者。对应到spring-boot中就是:
1.EventPublishingRunListener这个类封装了事件发布,
2.SpringApplicationEvent是spring-boot中定义的事件(上面说的6种事件),继承自ApplicationEvent(spring中定义的)
3.监听者 spring-boot并没有实现针对上述6种事件的监听者,这里用户可以自己实现监听者(上述6种事件)来注入spring boot容器启动流程,触发相应的事件。
例如:实现ApplicationListener
3. 定制Banner
启动时打印的banner可以通过在classpath下添加banner.txt来修改,或者通过指定banner.location设置文件。如果这个文件是不寻常编码,你可以设置banner.charset(默认UTF-8)。除了添加文本文件以外,你还可以在classpath下添加banner.gif、banner.jpg或banner.png,或者通过设置banner.image.location属性。图像将被转换成ASCII艺术形式,覆盖所有文本的形式并打印出来。
你可以在banner.txt里使用以下的占位符:
注:如果你想编程实现banner,可以使用org.springframework.boot.Banner接口实现自己的printBanner()方法。再通过SpringApplication.setBanner(…)方法设置。
你还可以使用spring.main.banner-mode属性来定义是在System.out(console)上打印、使用配置的logger(log)或不打印(off)。
4. 定制SpringApplication
如果默认的SpringApplication不能满足你的需求,你可以创建一个本地实例并定制它。例如,关闭banner:
1 | public static void main(String[] args) { |
SpringApplication的构造函数是spring beans配置源。在大多数情况下,它们会引用@Configuration类,但也可以引用XML配置或扫描包。
当然还可以使用application.properties来配置SpringApplication。
5. 链式构造器API
如果你需要构造一个ApplicationContext层次结构,或者你只是体验”链式”构造器API,你可以使用SpringApplicationBuilder。
SpringApplicationBuilder可让你链式调用多个方法,还包括 parent和child方法可让你创建一个层次结构。
例如:
1 | new SpringApplicationBuilder() |
6. Web environment(Web环境)
SpringApplication会尝试创建正确的ApplicationContext。默认情况下,会根据你开发的应用类型是否是web应用,来使用AnnotationConfigApplicationContext或AnnotationConfigEmbeddedWebApplicationContext。
确定”web environment”的算法非常简单(根据一些类的存在)。如果你需要覆盖默认的算法,你可以使用setWebEnvironment(boolean webEnvironment)设置。
也可以通过调用setApplicationContextClass()来完全控制ApplicationContext的类型。
注:在JUnit测试中使用SpringApplication时,通常需要调用setWebEnvironment(false)。
7. 访问应用参数
如果你要访问传入到SpringApplication.run()里的应用参数,你可以注入org.springframework.boot.ApplicationArguments。ApplicationArguments接口提供对原始String []参数以及解析option和non-option参数的访问:
1 | import org.springframework.boot.* |
8. 使用ApplicationRunner或CommandLineRunner
如果你想在SpringApplication启动后运行一些特殊代码,你可以实现ApplicationRunner或CommandLineRunner接口。这两个接口都提供了一个run方法,这个会在SpringApplication.run()完成之前调用。
CommandLineRunner接口提供以字符串数组的形式访问应用参数,而ApplicationRunner使用上述的ApplicationArguments。
1 | import org.springframework.boot.* |
如果你定义了多个CommandLineRunner或ApplicationRunner,并必须按照特定顺序执行。你可以通过实现org.springframework.core.Ordered接口或使用org.springframework.core.annotation.Order注解实现。
9. Application exit(应用退出)
每个SpringApplication都会注册一个JVM的shutdown hook(关闭钩子),以确保在退出的时候正常关闭ApplicationContext。可以使用所有标准的Spring生命周期回调。
此外,如果想在SpringApplication.exit()调用时返回特殊的退出码,可以实现org.springframework.boot.ExitCodeGenerator接口。然后可以将此退出码传递给System.exit()作为状态代码返回。
1 | @SpringBootApplication |
此外,ExitCodeGenerator接口还可以由异常来实现。遇到这样的异常时,Spring Boot会返回实现的getExitCode()方法的退出码。
10. @ServletComponentScan
在 SpringBootApplication 上使用@ServletComponentScan 注解后,Servlet、Filter、Listener 可以直接通过 @WebServlet、@WebFilter、@WebListener 注解自动注册,无需其他代码