一、注解和启动类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) })
在以上的注解中,我们要关注的有三个注解:
1.1 @SpringBootConfiguration:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }
@Configuration注解
配置注解的功能是将当前类标注为配置类,并将当前类里以 @Bean 注解标记的方法的实例
注入到spring容器中,实例名即为方法名。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration {
看到这个 @Component 注解, 意味也将会注册为bean, 其内部也可以依赖注入。@Configuration类通常使用
AnnotationConfigApplicationContext或其支持Web的变体AnnotationConfigWebApplicationContext进行引导。
后续可以继续深入学习spring更底层的运行机制......
1.2 @EnableAutoConfiguration
@EnableAutoConfiguration 注解启用自动配置,其可以帮助SpringBoot应用将所有符合
条件的 @Configuration 配置都加载到当前 IoC 容器之中。@EnableAutoConfiguration 借助 AutoConfigurationImportSelector
的帮助,而后者通过实现 electImports()方法来导出Configuration。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {
源码分析:
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry( autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } 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); }
AutoConfigurationImportSelector 类的 selectImports() 方法里面通过调用Spring Core 包里 SpringFactoriesLoader
类的 loadFactoryNames()方法
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; }
从 ClassPath下扫描所有的 META-INF/spring.factories 配置文件,并将spring.factories 文件中的 EnableAutoConfiguration
对应的配置项通过反射机制实例化为对应标注了 @Configuration 的形式的IoC容器配置类,然后注入IoC容器。
1.3 @ComponentScan
组件扫描,可自动发现和装配Bean,功能其实就是自动扫描并加载符合条件的组件或者bean定义,最终将这些bean定义加
载到IoC容器中。可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认
Spring框架实现会从声明@ComponentScan所在类的package进行扫描。默认扫描SpringApplication的run方法里的
Booter.class所在的包路径下文件,所以最好将该启动类放到根包路径下。
对于该注解,还可以通过 basePackages 属性来更细粒度的控制该注解的自动扫描范围,比如:
@ComponentScan(basePackages = {"cn.shu.controller","cn.shu.entity"}) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan {
二、SpringApplication类
我们也需要先构造 SpringApplication 类对象,然后调用该对象的 run() 方法。那么接下来就讲讲 SpringApplication 的构造
过程 以及其 run() 方法的流程,搞清楚了这个,那么也就搞清楚了SpringBoot应用是如何运行起来的!
2.1 实例一个SpringApplication对象,根据类名构造参数。
看一下源码:
@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)); // 推断应用的类型:创建的是 REACTIVE应用、SERVLET应用、NONE 三种中的某一种 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 找到*META-INF/spring.factories*中声明的所有ApplicationContextInitializer的实现类并将其实例化注册所有实现应用上下文初始化类,Application Context Initializers setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 找到*META-INF/spring.factories*中声明的所有ApplicationListener的实现类并将其实例化注册所有实现ApplicationListener的类 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //获得当前执行main方法的类对象,作为启动类。 this.mainApplicationClass = deduceMainApplicationClass(); }
# PropertySource Loaders org.springframework.boot.env.PropertySourceLoader= org.springframework.boot.env.PropertiesPropertySourceLoader, org.springframework.boot.env.YamlPropertySourceLoader # Run Listeners,仅仅一个实现类 org.springframework.boot.SpringApplicationRunListener= org.springframework.boot.context.event.EventPublishingRunListener # Error Reporters org.springframework.boot.SpringBootExceptionReporter= org.springframework.boot.diagnostics.FailureAnalyzers # Application Context Initializers org.springframework.context.ApplicationContextInitializer= org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer, org.springframework.boot.context.ContextIdApplicationContextInitializer, org.springframework.boot.context.config.DelegatingApplicationContextInitializer, org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer # Application Listeners org.springframework.context.ApplicationListener= org.springframework.boot.ClearCachesApplicationListener, org.springframework.boot.builder.ParentContextCloserApplicationListener, org.springframework.boot.context.FileEncodingApplicationListener, org.springframework.boot.context.config.AnsiOutputApplicationListener, org.springframework.boot.context.config.ConfigFileApplicationListener, org.springframework.boot.context.config.DelegatingApplicationListener, org.springframework.boot.context.logging.ClasspathLoggingApplicationListener, org.springframework.boot.context.logging.LoggingApplicationListener, org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor= org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor, org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor, org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor # Failure Analyzers org.springframework.boot.diagnostics.FailureAnalyzer= org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer, org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer # FailureAnalysisReporters org.springframework.boot.diagnostics.FailureAnalysisReporter= org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
2.2 怎么区别应用类别和上下文:REACTIVE、SERVLET、NONE
SERVLET:{ "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet"; private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer"; REACTIVE: private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler"; NONE:其他。 static WebApplicationType deduceFromApplicationContext( Class<?> applicationContextClass) { if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) { return WebApplicationType.SERVLET; } if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) { return WebApplicationType.REACTIVE; } return WebApplicationType.NONE; }
2.3 获得所有ApplicationContextInitializer.class的实例,放入初始化容器中。
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); UampServletInitializer extends SpringBootServletInitializer implements WebApplicationInitializer public class ServletContextApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext>, Ordered public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
2.4 使用 SpringFactoriesLoader查找并加载 classpath下文件中的所有可用的 ApplicationListener。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
三、简要分析SpringApplication类核心方法run()
3.1 源码如下:
public ConfigurableApplicationContext run(String... args) { //spring-framework提供了一个StopWatch类可以做类似任务执行时间控制,也就是封装了一个对开始时间,结束时间记录操作的Java类 StopWatch stopWatch = new StopWatch(); stopWatch.start();//计时任务的开始 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); //通过*SpringFactoriesLoader*检索*META-INF/spring.factories*, //找到声明的所有SpringApplicationRunListener的实现类并将其实例化, //之后逐个调用其started()方法,广播SpringBoot要开始执行了。 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile), //并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //如果为空,就设置ture. System.setProperty("spring.beaninfo.ignore", ignore.toString()); configureIgnoreBeanInfo(environment); // 打印banner Banner printedBanner = printBanner(environment); //根据webEnvironment的值来决定创建何种类型的ApplicationContext对象 //如果是web环境,则创建org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext //否则创建org.springframework.context.annotation.AnnotationConfigApplicationContext context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context); //为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext, //并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】, //之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成, //这里就包括通过**@EnableAutoConfiguration**导入的各种自动配置类。 prepareContext(context, environment, listeners, applicationArguments,printedBanner); //初始化所有自动配置类,调用ApplicationContext的refresh()方法 refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { //如果开启日志,则答应执行是时间 new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } //开始对上下文监听 listeners.started(context); //遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。 //该过程可以理解为是SpringBoot完成ApplicationContext初始化前的最后一步工作, //我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展 callRunners(context, applicationArguments); } catch (Throwable ex) { //调用异常分析器打印报告,调用所有的SpringApplicationRunListener的finished()方法将异常信息发布出去 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.2 流程图:
3.3 整个流程简要概括如下:
- 通过this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)
- 获取并创建 SpringApplicationRunListener 对象.
- 然后由 SpringApplicationRunListener 来发出 starting 消息
- 创建参数,并配置当前 SpringBoot 应用将要使用的 Environment
- 完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息
- 初始化 ApplicationContext,并设置 Environment,加载相关配置等
- 由 SpringApplicationRunListener 来发出 contextPrepared 消息,告知SpringBoot 应用使用的 ApplicationContext 已准备OK
- 将各种 beans 装载入 ApplicationContext,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 SpringBoot 应用使用的 ApplicationContext 已装填OK
- refresh ApplicationContext,完成IoC容器可用的最后一步
- 由 SpringApplicationRunListener 来发出 started 消息
- 完成最终的程序的启动
- 由 SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了
说明
- SpringBoot的启动过程,实际上就是对ApplicationContext的初始化过程。
- ApplicationContext创建后立刻为其设置Environmen,并由ApplicationContextInitializer对其进一步封装。
- 通过SpringApplicationRunListener在ApplicationContext初始化过程中各个时点发布各种广播事件,并由ApplicationListener负责接收广播事件。
- 初始化过程中完成IoC的注入,包括通过@EnableAutoConfiguration导入的各种自动配置类。
- 初始化完成前调用ApplicationRunner和CommandLineRunner的实现类。
3.4 如果要对SpringApplication进行扩展,我们可以选择如下三种方案:
- 创建ApplicationContextInitializer的实现类
- 创建ApplicationListener的实现类
- 创建ApplicationRunner和CommandLineRunner的实现类
四、SpringBootServletInitializer
4.1继承SpringBootServletInitializer
SpringBootServletInitializer用于替代传统mvc模式中的web.xml。如果你要使用外部的sevvlet容器,例如tomcat。
就需要继承该类并重写configure方法。在创建springboot项目时如果你创建的是war,则系统会默认提供一个继承类。
如果创建时选择的是jar,系统不会提供这个继承!需要自己写这个继承。
如果不继承该类会怎么样呢?答:项目无法使用外部容器启动
如果重复继承会怎么样呢?答:项目可以启动,但是会遇到很多不可预期的问题
public abstract class SpringBootServletInitializer implements WebApplicationInitializer { ...... public void onStartup(ServletContext servletContext) throws ServletException { this.logger = LogFactory.getLog(this.getClass()); WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext); if (rootAppContext != null) { servletContext.addListener(new ContextLoaderListener(rootAppContext) { public void contextInitialized(ServletContextEvent event) { } }); } else { this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context"); } }
4.2createRootApplicationContext方法的源码
SpringBootServletInitializer的执行过程,简单来说就是通过SpringApplicationBuilder构建并封装SpringApplication对象,
并最终调用SpringApplication的run方法的过程。
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { //创建SpringApplicationBuilder,并用其生产出SpringApplication对象 SpringApplicationBuilder builder = this.createSpringApplicationBuilder(); builder.main(this.getClass()); ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null); builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)}); } //初始化并封装SpringApplicationBuilder对象,为SpringApplication对象增加ApplicationContextInitializer和ApplicationListener做准备 builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)}); //指定创建的ApplicationContext类型 builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); //传递入口类,并构建SpringApplication对象 //可以通过configure()方法对SpringBootServletInitializer进行扩展 builder = this.configure(builder); builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)}); SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) { application.addPrimarySources(Collections.singleton(this.getClass())); } Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation"); if (this.registerErrorPageFilter) { application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class)); } //最后调用SpringApplication的run方法 return this.run(application); }
4.3扩展SpringBootServletInitializer
与扩展SpringApplication类似,ApplicationContextInitializer和ApplicationListener可以基于SpringApplicationBuilder提供
的public方法进行扩展。
public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { application.initializers(xxApplicationContextInitializer1,xxApplicationContextInitializer2); application.listeners(xxApplicationListener1,xxApplicationListener2) return application.sources(xxxApplication.class); } }