Spring Boot 启动原理

简介: Spring Boot 如何打成一个可执行jar包,以及以jar包方式运行起来(内部如何启动内嵌Tomcat)
  • 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.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-Spec: 1.8Spring-Boot-Version: 2.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源码的时候会感觉这咋有一个空的方法的疑问~在这里也算有一个解答了

相关文章
|
2月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
|
2月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
4月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
847 0
|
1月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
1月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
243 3
|
1月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
320 2
|
3月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
4月前
|
前端开发 Java 数据库连接
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
|
7月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
3374 113
|
4月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
163 0