spring boot启动概述
spring boot 框架凭借极简配置,一键运行和强大的第三方框架集成等特点,受到广大开发者的青睐,基本成为java开发中必不可少的基础的框架。spirng boot带给我们这么多便利的背后,它都做了些什么,让我们就跟随spirng boot的整个启动流程一探究竟。
上图可见spring boot的整个启动流程及各组件的相互调用关系。
- java程序由启动主类调用main()方法开始。
- 调用 SpringApplication的构造方法,实例一个Spirng应用对象。在构造方法里主要完成启动环境初始化工作,如,推断主类,spring应用类型,加载配置文件,读取spring.factories文件等。
- 调用run方法,所有的启动工作在该方法内完成,主要完成加载配置资源,准备上下文,创建上下文,刷新上下文,过程事件发布等。
整个启动流程细节我们跟着源码分析。
1.启动入口 (SrpingApplication)
大家熟悉的springboot的启动类,@SpringBootApplication + psvm(main方法)+ new SpringApplication().run(XXXX.class, args)
@SpringBootApplication
public class SummaryApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(); // 2
application.run(SummaryApplication.class, args); //3
// SpringApplication.run(SummaryApplication.class, args); 也可简化调用静态方法
}
}
1.1 @SpringBootApplication 注解
通过源码发现该注解只是@Configuration,@EnableAutoConfiguration,@ComponentScan 三个注解的组合,这是在springboot 1.5以后为这三个注解做的一个简写。接下来简单说下这三个注解的功能:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //1.1.1 注册为配置类
@EnableAutoConfiguration //1.1.2 配置可自动装配
@ComponentScan(excludeFilters = { //1.1.3 声明可扫描Bean
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
1.1.1 @SpringBootConfiguration
该注解就是spirng ioc容器中java config 配置方式的@Configuration ,注册当前类为spring ioc容器的配置类。
搭配@bean注解创建一个简单spring ioc配置类
@Configuration
public class Conf {
@Bean
public Car car() {
Car car = new Car();
car.setWheel(wheel());
return car;
}
@Bean
public Wheel wheel() {
return new Wheel();
}
}
1.1.2 @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) //最为重要
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
@EnableAutoConfiguration借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,会根据类路径中的jar依赖为项目进行自动配置,如:添加了spring-boot-starter-web依赖,会自动添加Tomcat和Spring MVC的依赖,Spring Boot会对Tomcat和Spring MVC进行自动配置。
最关键的要属@Import(EnableAutoConfigurationImportSelector.class) ,借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样,借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!
1.1.3 @ComponentScan
@ComponentScan这个注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。
注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
2.构造器(Constructor)
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@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));
//2.1 判断当前程序类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//2.2 使用SpringFactoriesLoader 实例化所有可用的初始器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//2.3 使用SpringFactoriesLoader 实例化所有可用的监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//2.4 配置应用主方法所在类
this.mainApplicationClass = deduceMainApplicationClass();
}
2.1 判断当前程序类型
根据classpath里面是否存在某个特征类
(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
3.启动方法(RUN)
初始化完成之后就进到了run方法,run方法完成了所有Spring的整个启动过程:
-
准备Environment
-
发布事件
-
创建上下文、bean
-
刷新上下文
-
结束,
其中穿插了很多监听器的动作,并且很多逻辑都是靠各种监听器的实现类执行的。
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//开启时钟计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//spirng 上下文
ConfigurableApplicationContext context = null;
//启动异常报告容器
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//开启设置,让系统模拟不存在io设备
configureHeadlessProperty();
// 3.1 初始化SpringApplicationRunListener 监听器,并进行封装
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//3.2 Environment 的准备
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment); // 打印标语 彩蛋
//3.3 创建上下文实例
context = createApplicationContext();
//异常播报器,默认有org.springframework.boot.diagnostics.FailureAnalyzers
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//3.4 容器初始化
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//3.5 刷新上下文容器
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;
}
3.1 SpringApplicationRunListener 的使用
首先通过getSpringFactoriesInstances 获取到所有实现SpringApplicationRunListener 接口的实例,默认情况下该接口的实现类只有 EventPublishingRunListener 他的主要作用是作为springboot 的一个广播器
public interface SpringApplicationRunListener {
/**EventPublishingRunListener 前期采用 SimpleApplicationEventMulticaster.multicastEvent(ApplicationEvent) 进行广播
**/
default void starting() {}
default void environmentPrepared(ConfigurableEnvironment environment) {}
default void contextPrepared(ConfigurableApplicationContext context) {}
default void contextLoaded(ConfigurableApplicationContext context) {}
/**
EventPublishingRunListener 后期采用 context.publishEvent(ApplicationEvent)
**/
default void started(ConfigurableApplicationContext context) {}
default void running(ConfigurableApplicationContext context) {}
default void failed(ConfigurableApplicationContext context, Throwable exception) {}
}
3.2 prepareEnvironment
一般在写业务代码时使用的都是只读类型的接口Environment
,该接口是对运行程序环境的抽象,是保存系统配置的中心,而在启动过程中使用的则是可编辑的ConfigurableEnvironment
。接口的UML类图如下,提供了合并父环境、添加active profile以及一些设置解析配置文件方式的接口。
其中一个比较重要的方法MutablePropertySources getPropertySources();
,该方法返回一个可编辑的PropertySources
,如果有在启动阶段自定义环境的PropertySources的需求,就可以通过该方法设置。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//根据不同环境不同的Enviroment (StandardServletEnvironment,StandardReactiveWebEnvironment,StandardEnvironment)
ConfigurableEnvironment environment = getOrCreateEnvironment();
//填充启动类参数到enviroment 对象
configureEnvironment(environment, applicationArguments.getSourceArgs());
//更新参数
ConfigurationPropertySources.attach(environment);
//发布事件
listeners.environmentPrepared(environment);
//绑定主类
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {//转换environment的类型,但这里应该类型和deduce的相同不用转换
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
//将现有参数有封装成proertySources
ConfigurationPropertySources.attach(environment);
return environment;
}
3.3 创建springApplicationContext 上下文
继承的三个父类接口里,Closeable
提供了关闭时资源释放的接口,Lifecycle
是提供对生命周期控制的接口(start\stop)以及查询当前运行状态的接口,ApplicationContext
则是配置上下文的中心配置接口,继承了其他很多配置接口,其本身提供查询诸如id、应用程序名等上下文档案信息的只读接口,以及构建自动装配bean的工厂。
- EnvironmentCapable
提供Environment接口。
- MessageSource
国际化资源接口。
- ApplicationEventPublisher
事件发布器。
- ResourcePatternResolver
资源加载器。
- HierarchicalBeanFactory、ListableBeanFactory
这两个都继承了bean容器的根接口BeanFactory
简而言之就是根据Web容器类型的不同来创建不用的上下文实例。
3.4 上下文初始化
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//绑定环境
context.setEnvironment(environment);
//如果application有设置beanNameGenerator、resourceLoader就将其注入到上下文中,并将转换工具也注入到上下文中
postProcessApplicationContext(context);
//调用初始化的切面
applyInitializers(context);
//发布ApplicationContextInitializedEvent事件
listeners.contextPrepared(context);
//日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
//如果bean名相同的话是否允许覆盖,默认为false,相同会抛出异常
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
// 这里获取到的是BootstrapImportSelectorConfiguration这个class,而不是自己写的启动来,这个class是在之前注册的BootstrapApplicationListener的监听方法中注入的
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载sources 到上下文中
load(context, sources.toArray(new Object[0]));
//发布ApplicationPreparedEvent事件
listeners.contextLoaded(context);
}
3.5 刷新上下文
AbstractApplicationContext
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//记录启动时间、状态,web容器初始化其property,复制listener
prepareRefresh();
//这里返回的是context的BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//beanFactory注入一些标准组件,例如ApplicationContextAwareProcessor,ClassLoader等
prepareBeanFactory(beanFactory);
try {
//给实现类留的一个钩子,例如注入BeanPostProcessors,这里是个空方法
postProcessBeanFactory(beanFactory);
// 调用切面方法
invokeBeanFactoryPostProcessors(beanFactory);
// 注册切面bean
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// bean工厂注册一个key为applicationEventMulticaster的广播器
initApplicationEventMulticaster();
// 给实现类留的一钩子,可以执行其他refresh的工作,这里是个空方法
onRefresh();
// 将listener注册到广播器中
registerListeners();
// 实例化未实例化的bean
finishBeanFactoryInitialization(beanFactory);
// 清理缓存,注入DefaultLifecycleProcessor,发布ContextRefreshedEvent
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
至此spring 启动主要工作基本完成,接下来发布AppStartedEvent事件,回调ApplicationRunner,CommandLineRunner等runner,发布applicationReadyEvent事件,spring 正式启动开始运行。