Spring Boot 启动原理

  • SpringBoot如何才能打包成一个可执行jar
   
<build>
  <plugins>
    <!-- 
            pom.xml 中打成一个可执行jar包必不可少的插件 
            否则,java -jar xxx.jar 报错jar中没有主清单属性
            其实在通过start.spring.io生成的项目中pom.xml文件默认有如下插件的
            但是,如果你是使用maven生成项目自己添加的话可能就容易不小心漏掉~
        -->
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>
  • 使用spring-boot-maven-plugin插件在打成jar包的时候做了写什么?
    • 把依赖的jar都打包到当前jar中,形成一个fat jar 解压当前jar可以看到依赖的jar包都在BOOT-INF/lib下
    • 生成一个MANIFEST.MF文件在META-INF目录下,内容如下
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: spring-boot-demo
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
## Spring Boot 启动类
Start-Class: com.fun.springbootdemo.SpringBootDemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.5.4
Created-By: Maven Jar Plugin 3.2.0
## 执行jar -jar 时运行的类JarLauncher去执行Boot启动类(对应的Start-Class)
Main-Class: org.springframework.boot.loader.JarLauncher

总结:Spring Boot使用spring-boot-maven-plugin插件生成了一个可执行jar,在打包成可执行jar过程中,一个是把依赖的jar都打包进来,另一个是生成一个MANIFEST.MF,这个文件中主要有两个部分需要注意一个是Start-Class定义了SpringBoot启动类,两一个是Main-Class是java -jar 执行时找的一个启动类(类加载器)


  • 进入Spring Boot 的Main-Class执行run方法后续流程梳理
// 一直点run方法,进入这个方法
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // ① 环境准备工作,如读取application.properties(yml)文件
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        // 这个地方就是执行打印banner的方法,如何替换banner可以尝试一下
        Banner printedBanner = this.printBanner(environment);
        // ② 选择创建一个对应类型的应用上下文
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // ④ 点进去或者debug跟着最后就会到Spring的refresh方法
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, listeners);
        throw new IllegalStateException(var10);
    }
    // 省略...
}

// ③ 这个是一个函数式接口写法(当前SpringBoot版本是2.5.4)对比下面
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
    try {
        switch(webApplicationType) {
            case SERVLET:
                // 默认启动情况下创建当前类型的上下文 (直接new)
                return new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE:
                return new AnnotationConfigReactiveWebServerApplicationContext();
            default:
                return new AnnotationConfigApplicationContext();
        }
    } catch (Exception var2) {
        throw new IllegalStateException("Unable create a default ApplicationContext instance, you may need a custom ApplicationContextFactory", var2);
    }
};
// 这个是SpringBoot 2.3.4版本的 利用的是反射创建对应类型的上下文
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        } catch (ClassNotFoundException var3) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
        }
    }
    // 通过反射方式
    return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

总结:从run方法运行到进行环境准备工作,读取到全局配置文件到根据应用类型创建对应应用上下文,以及调用this.refreshContext(context);最终调用到Spring的refresh方法完成整个启动流程(这个过程中是如何启动内嵌Tomcat,看接下来梳理~)


  • SpringBoot在启动的时候如何启动内嵌的Tomcat

SpringBoot利用了Spring的扩展机制在refresh方法内重写了onRefresh方法

protected void onRefresh() {
    super.onRefresh();

    try {
        // 创建web server
        this.createWebServer();
    } catch (Throwable var2) {
        throw new ApplicationContextException("Unable to start web server", var2);
    }
}
// 创建 web server
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    // 判断一下是否存在(存在时,可能是使用的外部web server 就不创建内嵌的web server)
    if (webServer == null && servletContext == null) {
        StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
        // ① 获取 web server factory
        ServletWebServerFactory factory = this.getWebServerFactory();
        createWebServer.tag("factory", factory.getClass().toString());
        // ③ 接下就是new Tomcat 设置对应的参数,一个内嵌web server创建完成
        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        createWebServer.end();
        this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
        this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
    } else if (servletContext != null) {
        try {
            this.getSelfInitializer().onStartup(servletContext);
        } catch (ServletException var5) {
            throw new ApplicationContextException("Cannot initialize servlet context", var5);
        }
    }

    this.initPropertySources();
}
// ②  获取 web server factory
protected ServletWebServerFactory getWebServerFactory() {
    // debug 可以看到获取到beanNames[0]="tomcatServletWebServerFactory"
    // 是因为SpringBoot 默认使用的Tomcat (还支持Jetty和Undertow)
    // org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration
    String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    if (beanNames.length == 0) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.");
    } else if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    } else {
        return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }
}


总结:SpringBoot一个可执行jar的生成到使用内嵌Tomcat启动的流程梳理完成了,整个流程走下来,感觉要比较熟悉SpringBoot的自动装配流程以及Spring的Bean生命周期以及其中的扩展点。比如onRefresh(),是不是在初次看的时候Spring源码的时候会感觉这咋有一个空的方法的疑问~在这里也算有一个解答了

上一篇:创建周期性计划任务cron


下一篇:配置office web app server 和sharepoint 2013绑定