- 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-Version1.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-Spec1.8 Spring-Boot-Version2.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源码的时候会感觉这咋有一个空的方法的疑问~在这里也算有一个解答了