深入学习 Spring Boot:Spring Boot启动分析(上)

前言

Spring Boot早就接触了,浏览了一遍官方文档,写过几个小Demo,但是对于其源码、底层实现不了解,现在终于有时间看看源码,学习学习了。本专题不定期更新,希望各位大佬不吝赐教^@^

学习工具:
idea 或 eclipse
Spring Boot 官方文档
Spring Boot API
Spring API

0、搭建一个简单的Spring Boot web程序

要想知道程序是怎么运行的,不跑跑程序怎么知道呢?而且在IDEA等开发工具可以帮助我们更好的阅读源码。我们先按照官方文档的指导,在idea上搭建一个简单的Spring Boot web应用程序:

a、新建maven项目,引入spring boot经典pom:

 <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.M3</version>
    </parent>

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <!-- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <!-- (you don't need this if you are using a .RELEASE version) -->
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/snapshot</url>
            <snapshots><enabled>true</enabled></snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <url>http://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/snapshot</url>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <url>http://repo.spring.io/milestone</url>
        </pluginRepository>
    </pluginRepositories>

b、编写入口程序:

package com.zhuangqf.learn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

/**
 * Created by zhuangqf on 9/3/17.
 */
@EnableAutoConfiguration
public class App {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(App.class, args);
    }
}

为了分析方便,我们删去了@RestController的注解。在后续的文章中,我还会就@RestController等注解做专门分析。

c、运行程序:

运行spring程序,可以直接运行App启动类的main函数,也可以在命令行上用maven运行:mvn spring-boot:run。idea对Spring十分友好,我们可以点左上角的Edit Configuration编辑我们的启动按钮,记得勾选Enable Debug Output

Edit Configuration

配置完成后点击run,console会有五彩斑斓的日志打印出来:

2017-09-03 16:59:27.946 DEBUG 10926 --- [           main] .c.l.ClasspathLoggingApplicationListener : Application started with classpath: [file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/charsets.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/deploy.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/cldrdata.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/dnsns.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/jaccess.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/jfxrt.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/localedata.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/nashorn.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/sunec.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/sunjce_provider.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/sunpkcs11.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/ext/zipfs.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/javaws.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/jce.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/jfr.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/jfxswt.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/jsse.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/management-agent.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/plugin.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/resources.jar, file:/usr/lib/jvm/jdk1.8.0_73/jre/lib/rt.jar, file:/home/zhuangqf/workspace/spring/SpringBootDemo/target/classes/, file:/home/zhuangqf/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.0.0.M3/spring-boot-starter-web-2.0.0.M3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/boot/spring-boot-starter/2.0.0.M3/spring-boot-starter-2.0.0.M3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/boot/spring-boot/2.0.0.M3/spring-boot-2.0.0.M3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.0.0.M3/spring-boot-autoconfigure-2.0.0.M3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.0.0.M3/spring-boot-starter-logging-2.0.0.M3.jar, file:/home/zhuangqf/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar, file:/home/zhuangqf/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar, file:/home/zhuangqf/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar, file:/home/zhuangqf/.m2/repository/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar, file:/home/zhuangqf/.m2/repository/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.jar, file:/home/zhuangqf/.m2/repository/org/springframework/spring-core/5.0.0.RC3/spring-core-5.0.0.RC3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/spring-jcl/5.0.0.RC3/spring-jcl-5.0.0.RC3.jar, file:/home/zhuangqf/.m2/repository/org/yaml/snakeyaml/1.18/snakeyaml-1.18.jar, file:/home/zhuangqf/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.0.0.M3/spring-boot-starter-json-2.0.0.M3.jar, file:/home/zhuangqf/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.9.0.pr4/jackson-databind-2.9.0.pr4.jar, file:/home/zhuangqf/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.9.0.pr4/jackson-annotations-2.9.0.pr4.jar, file:/home/zhuangqf/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.9.0.pr4/jackson-core-2.9.0.pr4.jar, file:/home/zhuangqf/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.9.0.pr4/jackson-datatype-jdk8-2.9.0.pr4.jar, file:/home/zhuangqf/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.0.pr4/jackson-datatype-jsr310-2.9.0.pr4.jar, file:/home/zhuangqf/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.9.0.pr4/jackson-module-parameter-names-2.9.0.pr4.jar, file:/home/zhuangqf/.m2/repository/com/fasterxml/jackson/module/jackson-module-kotlin/2.9.0.pr4/jackson-module-kotlin-2.9.0.pr4.jar, file:/home/zhuangqf/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.0.0.M3/spring-boot-starter-tomcat-2.0.0.M3.jar, file:/home/zhuangqf/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/8.5.16/tomcat-embed-core-8.5.16.jar, file:/home/zhuangqf/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/8.5.16/tomcat-embed-el-8.5.16.jar, file:/home/zhuangqf/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/8.5.16/tomcat-embed-websocket-8.5.16.jar, file:/home/zhuangqf/.m2/repository/org/hibernate/hibernate-validator/5.4.1.Final/hibernate-validator-5.4.1.Final.jar, file:/home/zhuangqf/.m2/repository/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final.jar, file:/home/zhuangqf/.m2/repository/org/jboss/logging/jboss-logging/3.3.1.Final/jboss-logging-3.3.1.Final.jar, file:/home/zhuangqf/.m2/repository/com/fasterxml/classmate/1.3.3/classmate-1.3.3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/spring-web/5.0.0.RC3/spring-web-5.0.0.RC3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/spring-beans/5.0.0.RC3/spring-beans-5.0.0.RC3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/spring-webmvc/5.0.0.RC3/spring-webmvc-5.0.0.RC3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/spring-aop/5.0.0.RC3/spring-aop-5.0.0.RC3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/spring-context/5.0.0.RC3/spring-context-5.0.0.RC3.jar, file:/home/zhuangqf/.m2/repository/org/springframework/spring-expression/5.0.0.RC3/spring-expression-5.0.0.RC3.jar, file:/home/zhuangqf/software/idea/lib/idea_rt.jar]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v2.0.0.M3)

2017-09-03 16:59:28.202  INFO 10926 --- [           main] com.zhuangqf.learn.App                   : Starting App on zhuangqinfa with PID 10926 (/home/zhuangqf/workspace/spring/SpringBootDemo/target/classes started by zhuangqf in /home/zhuangqf/workspace/spring/SpringBootDemo)
2017-09-03 16:59:28.204  INFO 10926 --- [           main] com.zhuangqf.learn.App                   : No active profile set, falling back to default profiles: default
2017-09-03 16:59:28.204 DEBUG 10926 --- [           main] o.s.boot.SpringApplication               : Loading source class com.zhuangqf.learn.App
2017-09-03 16:59:28.253  INFO 10926 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@54c562f7: startup date [Sun Sep 03 16:59:28 CST 2017]; root of context hierarchy
2017-09-03 16:59:28.258 DEBUG 10926 --- [           main] ConfigServletWebServerApplicationContext : Bean factory for org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@54c562f7: org.springframework.beans.factory.support.DefaultListableBeanFactory@43bc63a3: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,app]; root of factory hierarchy
2017-09-03 16:59:30.034 DEBUG 10926 --- [           main] ConfigServletWebServerApplicationContext : Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@ccb4b1b]
2017-09-03 16:59:30.034 DEBUG 10926 --- [           main] ConfigServletWebServerApplicationContext : Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@4097cac]
2017-09-03 16:59:30.581 DEBUG 10926 --- [           main] .s.b.w.e.t.TomcatServletWebServerFactory : Code archive: /home/zhuangqf/.m2/repository/org/springframework/boot/spring-boot/2.0.0.M3/spring-boot-2.0.0.M3.jar
2017-09-03 16:59:30.582 DEBUG 10926 --- [           main] .s.b.w.e.t.TomcatServletWebServerFactory : Code archive: /home/zhuangqf/.m2/repository/org/springframework/boot/spring-boot/2.0.0.M3/spring-boot-2.0.0.M3.jar
2017-09-03 16:59:30.582 DEBUG 10926 --- [           main] .s.b.w.e.t.TomcatServletWebServerFactory : None of the document roots [src/main/webapp, public, static] point to a directory and will be ignored.
2017-09-03 16:59:30.651  INFO 10926 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2017-09-03 16:59:30.680  INFO 10926 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2017-09-03 16:59:30.681  INFO 10926 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.16
2017-09-03 16:59:30.882  INFO 10926 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-09-03 16:59:30.882  INFO 10926 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2633 ms
2017-09-03 16:59:31.039 DEBUG 10926 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Added existing Servlet initializer bean 'dispatcherServletRegistration'; order=2147483647, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class]
2017-09-03 16:59:31.111 DEBUG 10926 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'characterEncodingFilter'; order=-2147483648, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/HttpEncodingAutoConfiguration.class]
2017-09-03 16:59:31.111 DEBUG 10926 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'hiddenHttpMethodFilter'; order=-10000, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.class]
2017-09-03 16:59:31.111 DEBUG 10926 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'httpPutFormContentFilter'; order=-9900, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.class]
2017-09-03 16:59:31.111 DEBUG 10926 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'requestContextFilter'; order=-105, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]
2017-09-03 16:59:31.115  INFO 10926 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-09-03 16:59:31.121  INFO 10926 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-09-03 16:59:31.122  INFO 10926 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-09-03 16:59:31.122  INFO 10926 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-09-03 16:59:31.122  INFO 10926 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-09-03 16:59:31.146 DEBUG 10926 --- [ost-startStop-1] o.s.b.w.s.f.OrderedRequestContextFilter  : Initializing filter 'requestContextFilter'
2017-09-03 16:59:31.147 DEBUG 10926 --- [ost-startStop-1] o.s.b.w.s.f.OrderedRequestContextFilter  : Filter 'requestContextFilter' configured successfully
2017-09-03 16:59:31.148 DEBUG 10926 --- [ost-startStop-1] .b.w.s.f.OrderedHttpPutFormContentFilter : Initializing filter 'httpPutFormContentFilter'
2017-09-03 16:59:31.148 DEBUG 10926 --- [ost-startStop-1] .b.w.s.f.OrderedHttpPutFormContentFilter : Filter 'httpPutFormContentFilter' configured successfully
2017-09-03 16:59:31.148 DEBUG 10926 --- [ost-startStop-1] .s.b.w.s.f.OrderedHiddenHttpMethodFilter : Initializing filter 'hiddenHttpMethodFilter'
2017-09-03 16:59:31.148 DEBUG 10926 --- [ost-startStop-1] .s.b.w.s.f.OrderedHiddenHttpMethodFilter : Filter 'hiddenHttpMethodFilter' configured successfully
2017-09-03 16:59:31.149 DEBUG 10926 --- [ost-startStop-1] s.b.w.s.f.OrderedCharacterEncodingFilter : Initializing filter 'characterEncodingFilter'
2017-09-03 16:59:31.149 DEBUG 10926 --- [ost-startStop-1] s.b.w.s.f.OrderedCharacterEncodingFilter : Filter 'characterEncodingFilter' configured successfully
2017-09-03 16:59:31.672  INFO 10926 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@54c562f7: startup date [Sun Sep 03 16:59:28 CST 2017]; root of context hierarchy
2017-09-03 16:59:31.828  INFO 10926 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-09-03 16:59:31.832  INFO 10926 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-09-03 16:59:31.890  INFO 10926 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-09-03 16:59:31.890  INFO 10926 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-09-03 16:59:31.958  INFO 10926 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-09-03 16:59:32.187  INFO 10926 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-09-03 16:59:32.196 DEBUG 10926 --- [           main] ConfigServletWebServerApplicationContext : Unable to locate LifecycleProcessor with name 'lifecycleProcessor': using default [org.springframework.context.support.DefaultLifecycleProcessor@54e7391d]
2017-09-03 16:59:32.226 DEBUG 10926 --- [           main] utoConfigurationReportLoggingInitializer : 


=========================
AUTO-CONFIGURATION REPORT
=========================

// 此处省略自动配置发现报告

2017-09-03 16:59:32.277  INFO 10926 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http)
2017-09-03 16:59:32.283  INFO 10926 --- [           main] com.zhuangqf.learn.App                   : Started App in 4.824 seconds (JVM running for 5.613)


1、从SpringApplication.run()出发

SpringApplication的API介绍如下:

SpringApplication,可用于从 Java main 方法引导和启动 Spring 应用程序。默认将执行以下步骤来引导应用程序:
a、创建适当的 ApplicationContext 实例 (取决于您的类路径)
b、注册 CommandLinePropertySource 以将命令行参数公开为 Spring 属性
c 、刷新应用程序上下文, 加载所有单独的 bean
d、触发任何 CommandLineRunner bean

在整个项目中,我们做的工作十分的简单:

  • 为App类添加@EnableAutoConfiguration注解;
  • 在main方法中调用SpringApplication.run(App.class, args)

然后Spring就帮我们做了大量的工作,最终启动了一个web程序。现在让我们从SpringApplication.run()出发看看Spring Boot到底是怎么启动的。

public static ConfigurableApplicationContext run(Class<?>[] primarySources,String[] args) {
    return new SpringApplication(primarySources).run(args);
}

run方法先将我们的启动类(App)的类名作为参数实例化了一个SpringApplication,然后调用了另一个run方法:run(args)

SpringApplication构造器:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = deduceWebApplicationType();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

其中:

  • resourceLoader为null(传入参数);
  • primarySources 里只有一个元素(App.class,传入参数);-
  • deduceWebApplicationType()推断当前环境是哪种Web环境(Servlet、Reactive),或者不是Web环境,判断逻辑为Classpath是够有以下类:
    存在org.springframework.web.reactive.DispatcherHandler且不存在org.springframework.web.servlet.DispatcherServletWebApplicationType.REACTIVE
    同时存在javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContextWebApplicationType.SERVLET
    否则为 WebApplicationType.NONE
    在这里this.webApplicationType = WebApplicationType.SERVLET
  • initializers是一个用来存放SpringApplication所需的ApplicationContextInitializer实例的ArrayList,这些实例是用来初始化应用程序的上下文环境的。getSpringFactoriesInstances方法可以根据类型和配置文件 "META-INF/spring.factories"(包括依赖的jar中打包的)加载相应的工厂类,具体见下文。以下是本项目实例化的Initializer:
initializers = {ArrayList@952}  size = 6
 0 = {DelegatingApplicationContextInitializer@1057} 
 1 = {ContextIdApplicationContextInitializer@1058} 
 2 = {ConfigurationWarningsApplicationContextInitializer@1059} 
 3 = {ServerPortInfoApplicationContextInitializer@1060} 
 4 = {SharedMetadataReaderFactoryContextInitializer@1061} 
 5 = {AutoConfigurationReportLoggingInitializer@1062} 
  • 与initializers类似,listeners是一个用来存放监听器的ArrayList,以下是本项目实例化的listeners:
listeners = {ArrayList@1055}  size = 10
 0 = {ConfigFileApplicationListener@1067} 
 1 = {AnsiOutputApplicationListener@1068} 
 2 = {LoggingApplicationListener@1069} 
 3 = {ClasspathLoggingApplicationListener@1070} 
 4 = {BackgroundPreinitializer@1071} 
 5 = {DelegatingApplicationListener@1072} 
 6 = {ParentContextCloserApplicationListener@1073} 
 7 = {ClearCachesApplicationListener@1074} 
 8 = {FileEncodingApplicationListener@1075} 
 9 = {LiquibaseServiceLocatorApplicationListener@1076} 
  • deduceMainApplicationClass()推断当前的应用程序的入口类,即我们的App.class。你可能要问,我们在一开始调用SpringApplication.run()的时候不是将App.class作为参数传入SpringApplication了吗?事实上,我们可以将多个Class作为数组参数传入SpringApplication,记住primarySources是一个Set。deduceMainApplicationClass会遍历线程的执行栈new RuntimeException().getStackTrace(),找到第一个具有公共main方法的类,并将其返回。在这里mainApplicationClass = App.class

至此,SpringBoot帮我们实例化了一个SpringApplication实例,初始化了以下成员变量:

  • ResourceLoader resourceLoader
  • Set<Class<?>> primarySources
  • WebApplicationType webApplicationType
  • List<ApplicationContextInitializer<?>> initializers
  • List<ApplicationListener<?>> listeners

接着调用该实例的run(),启动 Spring 应用程序。


1.1 从spring.factories中加载类的机制

在继续探究run方法之前,我们先来看一下遗留问题:getSpringFactoriesInstances方法。
在SpringApplication的初始化中,程序通过这个方法从classpath下的META-INF/spring.factories加载了ApplicationContextInitializer和ApplicationListener接口的多个实现类。以下是源码:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

代码很清晰:

  • 加载spring.factories,以type为key读取相应的值;
  • 实例化读取到的值;
  • 将实例排序并返回

SpringFactoriesLoader

SpringFactoriesLoader是Spring内部用来加载spring.factories配置文件的静态类。这些文件可以是我们项目新建的,也可以是依赖的jar包下打包引入的。

spring.factories是Spring项目的配置文件之一,位于META-INF目录下。其必须是 Properties 格式,即Key-Value格式。同时要求,key必须为接口或抽象类的完全限定名,value是一个以逗号分隔的实现类名称列表:
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2

SpringFactoriesLoader只有两个public方法:

  • loadFactoryNames 通过给定的接口或抽象类的类型(Class),加载配置文件中所有实现类的完全限定名(全类名);
  • loadFactories 通过给定的接口或抽象类的类型(Class),加载并实例化配置文件中所有实现类 ;

loadFactoryNames源码:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap result = (MultiValueMap)cache.get(classLoader);
    if(result != null) {
        return result;
    } else {
        try {
            Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result1 = new LinkedMultiValueMap();

            while(ex.hasMoreElements()) {
                URL url = (URL)ex.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry entry = (Entry)var6.next();
                    List factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                    result1.addAll((String)entry.getKey(), factoryClassNames);
                }
            }

            cache.put(classLoader, result1);
            return result1;
        } catch (IOException var9) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
        }
    }
}

代码并不难,但是它教会了我们怎么从classpath下读取我们自己写的配置文件,不局限于Spring,也不局限于Properties文件

  • classLoader.getResources()ClassLoader.getSystemResources(),这两个方法的区别在于,前者用的是我们指定的ClassLoader,可以自定义加载路径;或者用的是加载这个类的ClassLoader的,搜索的path是这个项目的classpath
  • 调用PropertiesLoaderUtils.loadProperties(),读取文件内容,并将其转为MultiValueMap。PropertiesLoaderUtils是Spring内部工具类,其实现就是传统的java读取Properties文件内容。用MultiValueMap保存数据的原因在于,spring.factories的value是一个以逗号分隔的实现类名称列表,且有多个文件,即一个key对应多个value,更重要的是,用map可以消除重复项。
  • 用map的getOrDefault(factoryClassName, Collections.emptyList())返回对应的value。用getOrDefault指定默认值为Collections.emptyList(),避免了返回null
  • 用ConcurrentReferenceHashMap实现缓存。文件IO是一件十分耗时的工作,特别是对于读取大量的文件,所以我们有必要缓存下对应的Map。ConcurrentReferenceHashMap是Java7引进的,基于WeakHashMap实现的同步HashMap,同时解决了同步和因引用引起的内存溢出问题,是实现缓存的一个简单安全的方法。

loadFactories源码:

public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
    Assert.notNull(factoryClass, "'factoryClass' must not be null");
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
    if (logger.isTraceEnabled()) {
        logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
    }
    List<T> result = new ArrayList<>(factoryNames.size());
    for (String factoryName : factoryNames) {
        result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
    }
    AnnotationAwareOrderComparator.sort(result);
    return result;
}

private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
    try {
        Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
        if (!factoryClass.isAssignableFrom(instanceClass)) {
            throw new IllegalArgumentException(
                    "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
        }
        return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
    }
    catch (Throwable ex) {
        throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
    }
}

loadFactories方法比loadFactoryNames多了一个实例化的功能,所以首先会调用loadFactoryNames加载配置文件中对应接口或抽象类的所有实现类的完全限定名,接着逐一进行实例化,最后返回排列好的实例的集合。
我们具体看一下实例化的过程:

  • 通过ClassUtils.forName()获取类名(String)对应的Class对象。ClassUtils.forName是Class.forName的替代实现,它还返回基础数据类型以及数组类型的Class对象(如“int.class”),还可以解析内部类为通用的表达方式(例如 "java.lang.Thread.State", 而不是 "java.lang.Thread$State")。
  • clazz..isAssignableFrom()与instanceof类似,只不过instanceof 针对的实例,而isAssignableFrom用来判断一个类Class1和另一个类Class2是否相同或是另一个类的超类或接口。 在通过反射获取实例前,应先做类型检查。
  • 之后就是通过反射获取Class对象的构造器,然后实例化。
  • 根据@Order 和 @Priority 注释(如果有的话),调用AnnotationAwareOrderComparator.sort(result);对实例进行排序。

AnnotationAwareOrderComparator
AnnotationAwareOrderComparator 是 OrderComparator 的扩展, 它支持 Spring 的有序接口以及 @Order 和 @Priority 注释, 并具有一个由有序实例提供的、重写静态定义的注释值的顺序值 (如果有的话)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容