- 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.0Spring-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.8Spring-Boot-Version2.5.4Created-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方法,进入这个方法publicConfigurableApplicationContextrun(String... args) { StopWatchstopWatch=newStopWatch(); stopWatch.start(); DefaultBootstrapContextbootstrapContext=this.createBootstrapContext(); ConfigurableApplicationContextcontext=null; this.configureHeadlessProperty(); SpringApplicationRunListenerslisteners=this.getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args); // ① 环境准备工作,如读取application.properties(yml)文件ConfigurableEnvironmentenvironment=this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); this.configureIgnoreBeanInfo(environment); // 这个地方就是执行打印banner的方法,如何替换banner可以尝试一下BannerprintedBanner=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) { (newStartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwablevar10) { this.handleRunFailure(context, var10, listeners); thrownewIllegalStateException(var10); } // 省略...} // ③ 这个是一个函数式接口写法(当前SpringBoot版本是2.5.4)对比下面ApplicationContextFactoryDEFAULT= (webApplicationType) -> { try { switch(webApplicationType) { caseSERVLET: // 默认启动情况下创建当前类型的上下文 (直接new)returnnewAnnotationConfigServletWebServerApplicationContext(); caseREACTIVE: returnnewAnnotationConfigReactiveWebServerApplicationContext(); default: returnnewAnnotationConfigApplicationContext(); } } catch (Exceptionvar2) { thrownewIllegalStateException("Unable create a default ApplicationContext instance, you may need a custom ApplicationContextFactory", var2); } }; // 这个是SpringBoot 2.3.4版本的 利用的是反射创建对应类型的上下文protectedConfigurableApplicationContextcreateApplicationContext() { Class<?>contextClass=this.applicationContextClass; if (contextClass==null) { try { switch(this.webApplicationType) { caseSERVLET: contextClass=Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext"); break; caseREACTIVE: contextClass=Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext"); break; default: contextClass=Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext"); } } catch (ClassNotFoundExceptionvar3) { thrownewIllegalStateException("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方法
protectedvoidonRefresh() { super.onRefresh(); try { // 创建web serverthis.createWebServer(); } catch (Throwablevar2) { thrownewApplicationContextException("Unable to start web server", var2); } }
// 创建 web serverprivatevoidcreateWebServer() { WebServerwebServer=this.webServer; ServletContextservletContext=this.getServletContext(); // 判断一下是否存在(存在时,可能是使用的外部web server 就不创建内嵌的web server)if (webServer==null&&servletContext==null) { StartupStepcreateWebServer=this.getApplicationStartup().start("spring.boot.webserver.create"); // ① 获取 web server factoryServletWebServerFactoryfactory=this.getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString()); // ③ 接下就是new Tomcat 设置对应的参数,一个内嵌web server创建完成this.webServer=factory.getWebServer(newServletContextInitializer[]{this.getSelfInitializer()}); createWebServer.end(); this.getBeanFactory().registerSingleton("webServerGracefulShutdown", newWebServerGracefulShutdownLifecycle(this.webServer)); this.getBeanFactory().registerSingleton("webServerStartStop", newWebServerStartStopLifecycle(this, this.webServer)); } elseif (servletContext!=null) { try { this.getSelfInitializer().onStartup(servletContext); } catch (ServletExceptionvar5) { thrownewApplicationContextException("Cannot initialize servlet context", var5); } } this.initPropertySources(); } // ② 获取 web server factoryprotectedServletWebServerFactorygetWebServerFactory() { // debug 可以看到获取到beanNames[0]="tomcatServletWebServerFactory"// 是因为SpringBoot 默认使用的Tomcat (还支持Jetty和Undertow)// org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfigurationString[] beanNames=this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length==0) { thrownewApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean."); } elseif (beanNames.length>1) { thrownewApplicationContextException("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源码的时候会感觉这咋有一个空的方法的疑问~在这里也算有一个解答了