本文将通过阅读源码方式分析SpringBoot应用的启动流程,不涉及Spring启动部分(有相应的文章介绍)。
本文不会对各个流程做展开分析,后续会有文章介绍详细流程。
SpringApplication类
应用启动入口
使用以下方式启动一个SpringBoot应用:- @SpringBootApplication
- public class SpringBootDemoApplication {
- public static void main(String[] args) {
- SpringApplication.run(SpringBootDemoApplication.class, args);
- }
- }
复制代码 run方法
- 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);
- }
- 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);
- // 获取应用env
- ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
- configureIgnoreBeanInfo(environment);
- // 打印banner
- Banner printedBanner = printBanner(environment);
- // 创建ApplicationContext
- context = createApplicationContext();
- exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
- new Class[] { ConfigurableApplicationContext.class }, context);
- // 一些准备工作
- prepareContext(context, environment, listeners, applicationArguments, printedBanner);
- // refresh ApplicationContext
- refreshContext(context);
- afterRefresh(context, applicationArguments);
- stopWatch.stop();
- if (this.logStartupInfo) {
- new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
- }
- // 调用listener
- listeners.started(context);
- // 调用ApplicationRunner和CommandLineRunner
- 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;
- }
复制代码 获取应用env
- private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
- ApplicationArguments applicationArguments) {
- // 创建StandardServletEnvironment, 会初始化四个PropertySource:
- // servletConfigInitParams, servletContextInitParams, systemProperties, systemEnvironment
- // 比如-Dserver.port=8888会在systemProperties中
- ConfigurableEnvironment environment = getOrCreateEnvironment();
- // 添加defaultProperties和命令行配置参数即CommandLinePropertySource
- // 通常都没有这两个配置
- configureEnvironment(environment, applicationArguments.getSourceArgs());
- // 使用ConfigurationPropertySourcesPropertySource封装并暴露所有的PropertySource集
- ConfigurationPropertySources.attach(environment);
- // 添加ApplicationEnvironmentPreparedEvent事件并触发multicastEvent加载应用配置文件
- listeners.environmentPrepared(environment);
- // 将spring.main.xx配置加载到SpringApplication对象
- bindToSpringApplication(environment);
- if (!this.isCustomEnvironment) {
- environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
- deduceEnvironmentClass());
- }
- ConfigurationPropertySources.attach(environment);
- return environment;
- }
复制代码 加载配置文件的入口在ConfigFileApplicationListener类中:- public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
- addPropertySources(environment, application.getResourceLoader());
- }
- protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
- // 添加RandomValuePropertySource
- RandomValuePropertySource.addToEnvironment(environment);
- // 加载配置文件
- new Loader(environment, resourceLoader).load();
- }
复制代码 加载配置文件的源码较多,此处不做记录,简单梳理一下流程:
- 加载active profile配置文件
- 如果配置了spring.config.additional-location或spring.config.location参数,会使用它们作为配置文件。如果这两个参数值是目录,则会从这两个目录下查找配置文件
- 默认从classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/目录下查找application-xx.properties或application-xx.yml文件
- 使用PropertiesPropertySourceLoader和YamlPropertySourceLoader解析配置文件
- 会将配置参数封装成OriginTrackedMapPropertySource类型对象,使用applicationConfig: [classpath:/application-dev.yml]之类的字符串作为PropertySource的名称
- 加载默认的application.properties或application.yml文件
- 解析出来的所有PropertySource都会添加到environment的propertySources中,propertySources是一个MutablePropertySources对象,管理着所有的PropertySource集,在这个过程中,添加的先后顺序决定了配置的优先级
创建ApplicationContext
- protected ConfigurableApplicationContext createApplicationContext() {
- Class<?> contextClass = this.applicationContextClass;
- if (contextClass == null) {
- try {
- switch (this.webApplicationType) {
- case SERVLET:
- // AnnotationConfigServletWebServerApplicationContext类
- contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
- break;
- case REACTIVE:
- // AnnotationConfigReactiveWebServerApplicationContext类
- contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
- break;
- default:
- // AnnotationConfigApplicationContext类
- contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
- }
- } catch (ClassNotFoundException ex) {
- throw new IllegalStateException("Unable create a default ApplicationContext", ex);
- }
- }
- return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
- }
复制代码

prepareContext
- private void prepareContext(
- ConfigurableApplicationContext context, ConfigurableEnvironment environment,
- SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments,
- Banner printedBanner) {
- context.setEnvironment(environment);
- postProcessApplicationContext(context);
- // Apply any ApplicationContextInitializers to the context before it is refreshed.
- // ApplicationContextInitializers集是在创建SpringApplication对象的时候初始化的
- applyInitializers(context);
- // 触发contextPrepared事件
- listeners.contextPrepared(context);
- if (this.logStartupInfo) {
- logStartupInfo(context.getParent() == null);
- logStartupProfileInfo(context);
- }
- // 获取BeanFactory并注册必要的Bean
- ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
- // 注册启动参数
- beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
- // 注册banner printer
- if (printedBanner != null) {
- beanFactory.registerSingleton("springBootBanner", printedBanner);
- }
- // 设置是否允许Bean覆盖,使用spring.main.allowBeanDefinitionOverriding参数配置
- if (beanFactory instanceof DefaultListableBeanFactory) {
- ((DefaultListableBeanFactory) beanFactory)
- .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
- }
- if (this.lazyInitialization) {
- context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
- }
- // Load the sources
- Set<Object> sources = getAllSources();
- // 将SpringApplication.run(Xxx.class, args)方法传入的Class注册到容器
- // 使用AnnotatedBeanDefinitionReader.register(Class<?>...)方法注册启动类
- load(context, sources.toArray(new Object[0]));
- // 触发contextLoaded事件
- listeners.contextLoaded(context);
- }
复制代码 refreshApplicationContext
- protected void refresh(ConfigurableApplicationContext applicationContext) {
- applicationContext.refresh();
- }
复制代码 调用的是ServletWebServerApplicationContext的refresh方法:- public final void refresh() throws BeansException, IllegalStateException {
- try {
- super.refresh();
- } catch (RuntimeException ex) {
- // 关闭web server
- WebServer webServer = this.webServer;
- if (webServer != null) {
- webServer.stop();
- }
- throw ex;
- }
- }
复制代码 绝大多数的refresh逻辑都在AbstractApplicationContext类里面,ServletWebServerApplicationContext中会在onRefresh阶段创建webServer:- protected void onRefresh() {
- super.onRefresh();
- try {
- createWebServer();
- } catch (Throwable ex) {
- throw new ApplicationContextException("Unable to start web server", ex);
- }
- }
复制代码 调用ApplicationRunner和CommandLineRunner
- private void callRunners(ApplicationContext context, ApplicationArguments args) {
- List<Object> runners = new ArrayList<>();
- runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
- runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
- AnnotationAwareOrderComparator.sort(runners);
- for (Object runner : new LinkedHashSet<>(runners)) {
- if (runner instanceof ApplicationRunner) {
- callRunner((ApplicationRunner) runner, args);
- }
- if (runner instanceof CommandLineRunner) {
- callRunner((CommandLineRunner) runner, args);
- }
- }
- }
复制代码 SpringBootApplication注解
指示一个配置类,该类声明一个或多个@Bean方法,并触发自动配置和组件扫描。这是一个方便的注解,相当于声明@Configuration、@EnableAutoConfiguration和@ComponentScan注解。- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
- @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
- public @interface SpringBootApplication {
- /**
- * Exclude specific auto-configuration classes such that they will never be applied.
- */
- @AliasFor(annotation = EnableAutoConfiguration.class)
- Class<?>[] exclude() default {};
- /**
- * Exclude specific auto-configuration class names such that they will never be
- * applied.
- */
- @AliasFor(annotation = EnableAutoConfiguration.class)
- String[] excludeName() default {};
- /**
- * Base packages to scan for annotated components. Use scanBasePackageClasses
- * for a type-safe alternative to String-based package names.
- */
- @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
- String[] scanBasePackages() default {};
- /**
- * Type-safe alternative to scanBasePackages for specifying the packages to
- * scan for annotated components. The package of each class specified will be scanned.
- */
- @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
- Class<?>[] scanBasePackageClasses() default {};
- /**
- * The BeanNameGenerator class to be used for naming detected components
- * within the Spring container.
- */
- @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
- Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
- /**
- * Specify whether @Bean methods should get proxied in order to enforce
- * bean lifecycle behavior, e.g. to return shared singleton bean instances even in
- * case of direct @Bean method calls in user code. This feature requires
- * method interception, implemented through a runtime-generated CGLIB subclass which
- * comes with limitations such as the configuration class and its methods not being
- * allowed to declare final.
- */
- @AliasFor(annotation = Configuration.class)
- boolean proxyBeanMethods() default true;
- }
复制代码 AutoConfigurationPackage注解
Registers packages with AutoConfigurationPackages. When no base packages or base package classes are specified, the package of the annotated class is registered.- @Configuration
- public @interface SpringBootConfiguration {
- @AliasFor(annotation = Configuration.class)
- boolean proxyBeanMethods() default true;
- }
复制代码 AutoConfigurationImportSelector类
DeferredImportSelector接口的实现类,处理自动装配,导出所有需要自动装配的类。
创建WebServer
SpringBoot会在onRefresh阶段创建webServer,首先从spring容器获取ServletWebServerFactory,然后调用getWebServer方法创建webServer。
getWebServer方法需要传入ServletContextInitializer集来初始化ServletContext。- @AutoConfigurationPackage
- @Import(AutoConfigurationImportSelector.class)
- public @interface EnableAutoConfiguration {
- /**
- * Exclude specific auto-configuration classes such that they will never be applied.
- */
- Class<?>[] exclude() default {};
- /**
- * Exclude specific auto-configuration class names such that they will never be
- * applied.
- * 当类路径下没有指定的类时,可以使用这个属性指定排除的类
- */
- String[] excludeName() default {};
- }
复制代码 我们开发者如果需要使用ServletContextInitializer来初始化ServletContext的话,也可以编写一个实现类,然后将其注册到spring容器即可。
另外,SpringBoot还会自动装配DispatcherServletAutoConfiguration类,这个类会创建DispatcherServlet和DispatcherServletRegistrationBean。DispatcherServlet是SpringWebMvc的最核心组件,DispatcherServletRegistrationBean实现了ServletContextInitializer接口,可以将DispatcherServlet注册到ServletContext。以TomcatServletWebServerFactory为例,这个类会通过TomcatStarter来调用所有的ServletContextInitializer,TomcatStarter实现了ServletContainerInitializer接口,Tomcat的ServletContext在启动阶段会调用ServletContainerInitializer的onStartup方法来初始化Servlet容器。
SpringBoot启动流程
- 初始化environment应用配置参数:servletConfigInitParams, servletContextInitParams, systemProperties, systemEnvironment及配置文件等
- 创建ApplicationContext对象,SpringBoot应用默认使用的是AnnotationConfigServletWebServerApplicationContext类
- prepareContext阶段:触发一些事件,将启动类注册到Spring容器
- refresh阶段:扫描应用组件,自动装配
- onRefresh阶段:创建并初始化WebServer
- 调用ApplicationRunner和CommandLineRunner
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |