美文网首页
SpringBoot配置外部Tomcat项目启动流程源码分析(一

SpringBoot配置外部Tomcat项目启动流程源码分析(一

作者: W998 | 来源:发表于2022-09-19 14:58 被阅读0次

    前言

    SpringBoot应用默认以Jar包方式并且使用内置Servlet容器(默认Tomcat),该种方式虽然简单但是默认不支持JSP并且优化容器比较复杂。故而我们可以使用习惯的外置Tomcat方式并将项目打War包。

    【1】创建项目并打War包

    ① 同样使用Spring Initializer方式创建项目

    a9d884dd2b0c6e5ce20418044a4a9710.jpeg

    ② 打包方式选择"war"

    5997c452519013a4fb460227587569e3.jpeg

    ③ 选择添加的模块

    8882759c018df2220748b987c6ff7efc.jpeg

    ④ 创建的项目图示

    041e3c894233a1d5f608f4600decbe5c.jpeg

    有三个地方需要注意:

    • pom中打包方式已经为war;
    • 对比默认为jar的项目多了ServletInitializer类;
    • 项目结构没有src/main/webapp,且没有WEB/INF web.xml。

    ServletInitializer类如下:

    public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(SpringbootwebprojectApplication.class);
    }
    }
    

    pom文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.web</groupId>
    <artifactId>springbootwebproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>springbootwebproject</name>
    <description>Demo project for Spring Boot</description>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    </properties>
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--这里修改了内置Tomcat的作用域-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>
    </project>
    

    ⑤ 补全项目结构

    第一种方式,手动创建src/main/webapp, WEB/INF以及web.xml。

    第二种方式,使用idea创建,步骤如下:

    1.如下图所示,点击项目结构图标

    da44c9ad81d7b4f9e98a1f064c94358f.jpeg

    2.创建src/main/webapp

    13252064f8fac349d5570ba62b49c0fe.jpeg

    3.创建web.xml

    cfc979eeb6a27b5c3efe02f651cc7030.jpeg 835c404881f732e7045abcc95192abf7.jpeg

    此时项目结构图如下:

    71a1ad0e3eb6f3ebc604f28f873ff3fc.jpeg

    【2】使用外部配置的Tomcat启动项目

    ① 点击"Edit Configurations…"添加Tomcat。

    66c7a3c696938de648bef2da68837d92.jpeg

    ② 设置Tomcat、JDK和端口

    2c3084ecf925cffe27fc380214d02118.jpeg

    ③ 部署项目

    d529764c18e406fbf217bcc1f0781dc2.jpeg a5f986464d9113f3b27bc15a9dddf736.jpeg

    ④ 启动项目

    1df28109ffdb0da9be5245d2aec88023.jpeg

    此时如果webapp 下有index.html,index.jsp,则会默认访问index.html。

    如果只有index.jsp,则会访问index.jsp;如果webapp下无index.html或index.jsp,则从静态资源文件夹寻找index.html;如果静态资源文件夹下找不到index.html且项目没有对"/"进行额外拦截处理,则将会返回默认错误页面。

    index.html显示如下图:

    754db2a7791bbcb7285df65ccecf9736.jpeg

    【3】SpringBoot 使用外部Tomcat启动原理

    ① 首先看Servlet3.0中的规范

    • javax.servlet.ServletContainerInitializer(其是一个接口) 类是通过JAR服务API查找的。对于每个应用程序,ServletContainerInitializer的一个实例是由容器在应用程序启动时创建。
    • 提供servletcontainerinitializer实现的框架必须将名为javax.servlet的文件捆绑到jar文件的META-INF/services目录中。根据JAR服务API,找到指向ServletContainerInitializer的实现类。
    • 除了ServletContainerInitializer 之外,还有一个注解–@HandlesTypes。ServletContainerInitializer 实现上的handlesTypes注解用于寻找感兴趣的类–要么是@HandlesTypes注解指定的类,要么是其子类。
    • 不管元数据完成的设置如何,都将应用handlesTypes注解。
    • ServletContainerInitializer实例的onStartup 方法将在应用程序启动时且任何servlet侦听器事件被激发之前被调用。
    • ServletContainerInitializer 的onStartup 方法调用是伴随着一组类的(Set<Class<?>> webAppInitializerClasses),这些类要么是initializer的扩展类,要么是添加了@HandlesTypes注解的类。将会依次调用webAppInitializerClasses实例的onStartup方法。

    总结以下几点:

    1)服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面
    ServletContainerInitializer实例;

    2)jar包的META-INF/services文件夹下,有一个名为
    javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名;

    如下图所示:

    a65a2ca3d9b8fda25860129a660d0fa4.jpeg

    3)还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

    4)容器启动过程中首先调用
    ServletContainerInitializer 实例的onStartup方法。

    ServletContainerInitializer 接口如下:

    public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
    }
    

    ② 步骤分析如下

    第一步,Tomcat启动

    fba5f2d4ce6f6c16e54d9d8c7c8455d9.jpeg

    第二步,根据Servlet3.0规范,找到
    ServletContainerInitializer ,进行实例化

    jar包路径:

    org\springframework\spring-web\4.3.14.RELEASE\
    spring-web-4.3.14.RELEASE.jar!\METAINF\services\
    javax.servlet.ServletContainerInitializer:
    

    Spring的web模块里面有这个文件:

    org.springframework.web.SpringServletContainerInitializer
    
    f01fbf21f0f22428094cf36f00cd25ac.jpeg

    第三步,创建实例

    SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set集合,为这些WebApplicationInitializer类型的类创建实例并遍历调用其onStartup方法。

    SpringServletContainerInitializer 源码如下(调用其onStartup方法):

    //感兴趣的类为WebApplicationInitializer及其子类
    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    //先调用onStartup方法,会传入一系列webAppInitializerClasses
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
    throws ServletException {
    List<WebApplicationInitializer> initializers = new LinkedList<>();
    if (webAppInitializerClasses != null) {
    //遍历感兴趣的类
    for (Class<?> waiClass : webAppInitializerClasses) {
    // Be defensive: Some servlet containers provide us with invalid classes,
    // no matter what @HandlesTypes says...
    //判断是不是接口,是不是抽象类,是不是该类型
    if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
    WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
    try {
    //实例化每个initializer并添加到initializers中
    initializers.add((WebApplicationInitializer)
    ReflectionUtils.accessibleConstructor(waiClass).newInstance());
    }
    catch (Throwable ex) {
    throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
    }
    }
    }
    }
    if (initializers.isEmpty()) {
    servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
    return;
    }
    servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
    AnnotationAwareOrderComparator.sort(initializers);
    //依次调用initializer的onStartup方法。
    for (WebApplicationInitializer initializer : initializers) {
    initializer.onStartup(servletContext);
    }
    }
    
    3919d225abaeb4ebed863ac1f491d218.jpeg

    如上所示,在
    SpringServletContainerInitializer方法中又调用每一个initializer的onStartup方法。即先调用SpringServletContainerInitializer实例的onStartup方法,在onStartup()方法内部又遍历每一个WebApplicationInitializer类型的实例,调用其onStartup()方法。

    WebApplicationInitializer(Web应用初始化器)是什么?

    在Servlet 3.0+环境中提供的一个接口,以便编程式配置ServletContext而非传统的xml配置。该接口的实例被
    SpringServletContainerInitializer自动检测(@HandlesTypes(WebApplicationInitializer.class)这种方式)。而SpringServletContainerInitializer是Servlet 3.0+容器自动引导的。通过WebApplicationInitializer,以往在xml中配置的DispatcherServlet、Filter等都可以通过代码注入。你可以不用直接实现WebApplicationInitializer,而选择继承AbstractDispatcherServletInitializer。

    WebApplicationInitializer类型的类如下图:

    b522e2809c52ec0f416a4b03ab3703b1[0].jpeg

    可以看到,将会创建我们的
    com.web.ServletInitializer(继承自SpringBootServletInitializer)实例,并调用onStartup方法。

    第四步:我们的
    SpringBootServletInitializer的实例(com.web.ServletInitializer)会被创建对象,并执行onStartup方法(com.web.ServletInitializer继承自SpringBootServletInitializer,故而会调用SpringBootServletInitializer的onStartup方法)

    SpringBootServletInitializer源码如下:

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    // Logger initialization is deferred in case an ordered
    // LogServletContextInitializer is being used
    this.logger = LogFactory.getLog(getClass());
    //创建WebApplicationContext
    WebApplicationContext rootAppContext = createRootApplicationContext(
    servletContext);
    if (rootAppContext != null) {
    //如果根容器不为null,则添加监听--注意这里的ContextLoaderListener,
    //contextInitialized方法为空,因为默认application context已经被初始化
    servletContext.addListener(new ContextLoaderListener(rootAppContext) {
    @Override
    public void contextInitialized(ServletContextEvent event) {
    // no-op because the application context is already initialized
    }
    });
    }
    else {
    this.logger.debug("No ContextLoaderListener registered, as "
    + "createRootApplicationContext() did not "
    + "return an application context");
    }
    }
    

    可以看到做了两件事:创建RootAppContext 和为容器添加监听。

    创建WebApplicationContext 源码如下:

    protected WebApplicationContext createRootApplicationContext(
    ServletContext servletContext) {
    //创建SpringApplicationBuilder --这一步很关键
    SpringApplicationBuilder builder = createSpringApplicationBuilder();
    //设置应用主启动类--本文这里为com.web.ServletInitializer
    builder.main(getClass());
    
    */从servletContext中获取servletContext.getAttribute(
    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)作为parent。第一次获取肯定为null
    */
    ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
    if (parent != null) {
    this.logger.info("Root context already created (using as parent).");
    servletContext.setAttribute(
    //以将ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE重置为null
    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
    //注册一个新的ParentContextApplicationContextInitializer--包含parent
    builder.initializers(new ParentContextApplicationContextInitializer(parent));
    }
    //注册ServletContextApplicationContextInitializer--包含servletContext
    builder.initializers(
    new ServletContextApplicationContextInitializer(servletContext));
    //设置applicationContextClass为AnnotationConfigServletWebServerApplicationContext
    builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
    builder = configure(builder);
    //添加监听器
    builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
    //返回一个准备好的SpringApplication ,准备run-很关键
    SpringApplication application = builder.build();
    if (application.getAllSources().isEmpty() && AnnotationUtils
    .findAnnotation(getClass(), Configuration.class) != null) {
    application.addPrimarySources(Collections.singleton(getClass()));
    }
    Assert.state(!application.getAllSources().isEmpty(),
    "No SpringApplication sources have been defined. Either override the "
    + "configure method or add an @Configuration annotation");
    // Ensure error pages are registered
    if (this.registerErrorPageFilter) {
    application.addPrimarySources(
    Collections.singleton(ErrorPageFilterConfiguration.class));
    }
    //启动应用
    return run(application);
    }
    

    【4】createRootApplicationContext详细流程源码分析

    ① createRootApplicationContext().createSpringApplicationBuilder()

    跟踪代码到:

    public SpringApplicationBuilder(Class<?>... sources) {
    this.application = createSpringApplication(sources);
    }
    

    此时的Sources为空,继续跟踪代码:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //web应用类型--Servlet
    this.webApplicationType = deduceWebApplicationType();
    获取ApplicationContextInitializer,也是在这里开始首次加载spring.factories文件
    setInitializers((Collection) getSpringFactoriesInstances(
    ApplicationContextInitializer.class));
    这里是第二次加载spring.factories文件
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
    }
    
    f87b1f1793863c8f19f1543784c7a851.jpeg

    ApplicationContextInitializer是spring组件spring-context组件中的一个接口,主要是spring ioc容器刷新之前的一个回调接口,用于处于自定义逻辑。

    ApplicationContextInitializer(应用上下文初始化器)是什么?


    ConfigurableApplicationContext-Spring IOC容器称为“已经被刷新”状态前的一个回调接口去初始化ConfigurableApplicationContext。通常用于需要对应用程序上下文进行某些编程初始化的Web应用程序中。例如,与ConfigurableApplicationContext#getEnvironment() 对比,注册property sources或激活配置文件。另外ApplicationContextInitializer(和子类)相关处理器实例被鼓励使用去检测org.springframework.core.Ordered接口是否被实现或是否存在org.springframework.core.annotation.Order注解,如果存在,则在调用之前对实例进行相应排序。

    spring.factories文件中的实现类:

    # 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.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.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
    
    ce89a96d69e103c302c88a495066a266.jpeg

    设置WebApplicationType

    private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
    && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
    return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
    if (!ClassUtils.isPresent(className, null)) {
    return WebApplicationType.NONE;
    }
    }
    return WebApplicationType.SERVLET;
    }
    

    这里主要是通过判断REACTIVE相关的字节码是否存在,如果不存在,则web环境即为SERVLET类型。这里设置好web环境类型,在后面会根据类型初始化对应环境。

    设置Initializer–
    ApplicationContextInitializer类型

    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;
    }
    

    这里ClassLoader 获取的是线程上下文类加载器,这里使用的是Tomcat启动:


    ee2a435ad8c51cfc01418e2ef77ac441.jpeg

    Set<String> names如下:

    c170581f25f8be240095bcb33b3092e2.jpeg

    获取了6个instance:

    7d808669524d126357b79cb65b59301d.jpeg

    设置监听–ApplicationListener类型

    此时的type为ApplicationListener,Set<String> names如下:

    9632f6bdb8961eb9fab9724ec1dee6aa[0].jpeg

    至此SpringApplicationBuilder创建完毕。

    ② 添加
    ServletContextApplicationContextInitializer

    builder.initializers(
    new ServletContextApplicationContextInitializer(servletContext));
    

    此时SpringApplication Initializers和Listener如下:

    d7007f37955fdc17de8302d8948eb775.jpeg

    ③ 设置
    application.setApplicationContextClass

    7db2ab778132b6ae932b2edb0b1383bc[0].jpeg

    ④ builder = configure(builder);

    此时调用我们的ServletInitializer的configure方法:

    357f32e08e69d2d47d2cb0b425d02f89.jpeg efe78c3d2ff751b15898ec389dc7602f.jpeg

    ⑤ SpringApplication application = builder.build()创建应用

    把我们的主类添加到application 中:

    27474d190ce9b6f2310f0165721724cb.jpeg

    ⑥ 将
    ErrorPageFilterConfiguration添加到Set<Class<?>> primarySources

    d96dbcb9c688ad0211c1a20c6a9d9bad.jpeg

    接下来该run(application)了注意直到此时,我们让没有创建我们想要的容器,容器将会在run(application)中创建。

    SpringApplication.run源码如下所示:

    /**
    run Spring application,创建并刷新一个新的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();
    ConfigurableApplicationContext context = null;
    //异常报告集合
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    //java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很
    //多监控工具如jconsole 需要将该值设置为true,系统变量默认为true
    configureHeadlessProperty();
    
    //第一步:获取并启动监听器 SpringApplicationRunListener只有一个实现类EventPublishingRunListener,
    //EventPublishingRunListener有一个SimpleApplicationEventMulticaster
    //SimpleApplicationEventMulticaster有一个defaultRetriver
    //defaultRetriver有个属性为applicationListeners
    //每一次listeners.XXX()方法调用,都将会广播对应事件给applicationListeners监听器处理
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();//run方法第一次被调用时,调用listeners.starting();
    try {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    args);
    //第二步:构造容器环境
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
    applicationArguments);
    //设置需要忽略的bean
    configureIgnoreBeanInfo(environment);
    //打印banner
    Banner printedBanner = printBanner(environment);
    //第三步:创建容器
    context = createApplicationContext();
    //第四步:实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
    exceptionReporters = getSpringFactoriesInstances(
    SpringBootExceptionReporter.class,
    new Class[] { ConfigurableApplicationContext.class }, context);
    //第五步:准备容器
    prepareContext(context, environment, listeners, applicationArguments,
    printedBanner);
    //第六步:刷新容器
    refreshContext(context);
    //第七步:刷新容器后的扩展接口
    afterRefresh(context, applicationArguments);
    stopWatch.stop();
    if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass)
    .logStarted(getApplicationLog(), stopWatch);
    }
    //容器已经被刷新,但是CommandLineRunners和ApplicationRunners还没有被调用
    listeners.started(context);
    //调用CommandLineRunner和ApplicationRunner的run方法
    callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
    handleRunFailure(context, ex, exceptionReporters, listeners);
    throw new IllegalStateException(ex);
    }
    try {
    //在run结束前,且调用CommandLineRunner和ApplicationRunner的run方法后,调用
    listeners.running(context);
    }
    catch (Throwable ex) {
    handleRunFailure(context, ex, exceptionReporters, null);
    throw new IllegalStateException(ex);
    }
    return context;
    }
    

    【5】SpringApplication.run方法详细分析-获取并启动监听器

    ① 获取监听器getRunListeners

    SpringApplicationRunListeners listeners = getRunListeners(args);
    

    跟进
    SpringApplication.getRunListeners方法(返回SpringApplicationRunListeners):

    private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
    SpringApplicationRunListener.class, types, this, args));
    }
    

    上面可以看到,args本身默认为空,但是在获取监听器的方法中,
    getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)将当前对象作为参数,该方法用来获取spring.factories对应的监听器:

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
    Class<?>[] parameterTypes, Object... args) {
    //获取类加载器 WebappClassLoader
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    //根据类加载器,获取SpringApplicationRunListener(type)相关的监听器
    Set<String> names = new LinkedHashSet<>(
    SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //创建factories
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
    classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
    }
    

    Set<String> names如下:

    2425d0bb1a14ea46911a2e6aeb9d988e.jpeg

    整个 springBoot 框架中获取factories的方式统一如下:

    @SuppressWarnings("unchecked")
    private <T> List<T> createSpringFactoriesInstances(Class<T> type,
    Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
    Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
    try {
    // //装载class文件到内存
    Class<?> instanceClass = ClassUtils.forName(name, classLoader);
    Assert.isAssignable(type, instanceClass);
    Constructor<?> constructor = instanceClass
    .getDeclaredConstructor(parameterTypes);
    //主要通过反射创建实例
    T instance = (T) BeanUtils.instantiateClass(constructor, args);
    instances.add(instance);
    }
    catch (Throwable ex) {
    throw new IllegalArgumentException(
    "Cannot instantiate " + type + " : " + name, ex);
    }
    }
    return instances;
    }
    

    上面通过反射获取实例时会触发
    EventPublishingRunListener的构造函数。如下图所示将会把application的listener添加到SimpleApplicationEventMulticaster initialMulticaster的ListenerRetriever defaultRetriever的Set<ApplicationListener<?>> applicationListeners中:

    e6737ab2187e161a0d7d6c5253a28703.jpeg

    重点来看一下addApplicationListener方法:

    public void addApplicationListener(ApplicationListener<?> listener) {
    synchronized (this.retrievalMutex) {
    // Explicitly remove target for a proxy, if registered already,
    // in order to avoid double invocations of the same listener.
    Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
    if (singletonTarget instanceof ApplicationListener) {
    this.defaultRetriever.applicationListeners.remove(singletonTarget);
    }
    this.defaultRetriever.applicationListeners.add(listener);
    this.retrieverCache.clear();
    }
    }
    

    上述方法定义在
    SimpleApplicationEventMulticaster父类AbstractApplicationEventMulticaster中。关键代码为this.defaultRetriever.applicationListeners.add(listener);,这是一个内部类,用来保存所有的监听器。也就是在这一步,将spring.factories中的监听器传递到SimpleApplicationEventMulticaster中。

    继承关系如下:

    1ce47504712f0a0cd948322181de5eee.jpeg

    ② listeners.starting()–
    SpringApplicationRunListener启动–监听器第一次处理事件

    aeeac4c7659c80c6c07629b2a9538f3e[0].jpeg

    listeners.starting();,获取的监听器为
    EventPublishingRunListener,从名字可以看出是启动事件发布监听器,主要用来发布启动事件。

    SpringApplicationRunListener是run()方法的监听器,其只有一个实现类EventPublishingRunListener。SpringApplicationRunListeners是SpringApplicationRunListener的集合类。

    也就是说将会调用
    EventPublishingRunListener的starting()方法。

    public void starting() {
    //关键代码,这里是创建application启动事件`ApplicationStartingEvent`
    this.initialMulticaster.multicastEvent(
    new ApplicationStartingEvent(this.application, this.args));
    }
    

    EventPublishingRunListener这个是springBoot框架中最早执行的监听器,在该监听器执行started()方法时,会继续发布事件,也就是事件传递。这种实现主要还是基于spring的事件机制。

    继续跟进
    SimpleApplicationEventMulticaster,有个核心方法:

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    // //获取线程池,如果为空则同步处理。这里线程池为空,还未没初始化。
    Executor executor = getTaskExecutor();
    if (executor != null) {
    异步发送事件
    executor.execute(() -> invokeListener(listener, event));
    }
    else {
    // //同步发送事件
    invokeListener(listener, event);
    }
    }
    }
    

    ApplicationListener<E extends ApplicationEvent>接口有个抽象方法onApplicationEvent(E event)子类必须实现。该方法用来处理对应事件。

    其中getApplicationListeners(event, type)主要有四种listener:

    • LoggingApplicationListener(处理日志)
    • BackgroundPreinitializer
    • DelegatingApplicationListener
    • LiquibaseServiceLocatorApplicationListener

    这是springBoot启动过程中,第一处根据类型,执行监听器的地方。根据发布的事件类型从上述10种监听器中选择对应的监听器进行事件发布,当然如果继承了 springCloud或者别的框架,就不止10个了。这里选了一个 springBoot 的日志监听器来进行讲解,核心代码如下:

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
    //在springboot启动的时候
    if (event instanceof ApplicationStartedEvent) {
    onApplicationStartedEvent((ApplicationStartedEvent) event);
    }
    //springboot的Environment环境准备完成的时候
    else if (event instanceof ApplicationEnvironmentPreparedEvent) {
    onApplicationEnvironmentPreparedEvent(
    (ApplicationEnvironmentPreparedEvent) event);
    }
    //在springboot容器的环境设置完成以后
    else if (event instanceof ApplicationPreparedEvent) {
    onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    }
    //容器关闭的时候
    else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
    .getApplicationContext().getParent() == null) {
    onContextClosedEvent();
    }
    //容器启动失败的时候
    else if (event instanceof ApplicationFailedEvent) {
    onApplicationFailedEvent();
    }
    }
    

    因为我们的事件类型为ApplicationEvent,所以会执行onApplicationStartedEvent((ApplicationStartedEvent) event);。springBoot会在运行过程中的不同阶段,发送各种事件,来执行对应监听器的对应方法。

    相关文章

      网友评论

          本文标题:SpringBoot配置外部Tomcat项目启动流程源码分析(一

          本文链接:https://www.haomeiwen.com/subject/uwsportx.html