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

相关文章
|
24天前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
28 0
|
4月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
4月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
17天前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
28天前
|
Java Spring
SpringBoot自动装配的原理
在Spring Boot项目中,启动引导类通常使用`@SpringBootApplication`注解。该注解集成了`@SpringBootConfiguration`、`@ComponentScan`和`@EnableAutoConfiguration`三个注解,分别用于标记配置类、开启组件扫描和启用自动配置。
54 17
|
17天前
|
Java 容器
springboot自动配置原理
启动类@SpringbootApplication注解下,有三个关键注解 (1)@springbootConfiguration:表示启动类是一个自动配置类 (2)@CompontScan:扫描启动类所在包外的组件到容器中 (3)@EnableConfigutarion:最关键的一个注解,他拥有两个子注解,其中@AutoConfigurationpackageu会将启动类所在包下的所有组件到容器中,@Import会导入一个自动配置文件选择器,他会去加载META_INF目录下的spring.factories文件,这个文件中存放很大自动配置类的全类名,这些类会根据元注解的装配条件生效,生效
|
26天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
39 2
|
2月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
66 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
2月前
|
Java Spring 容器
springboot @RequiredArgsConstructor @Lazy解决循环依赖的原理
【10月更文挑战第15天】在Spring Boot应用中,循环依赖是一个常见问题,当两个或多个Bean相互依赖时,会导致Spring容器陷入死循环。本文通过比较@RequiredArgsConstructor和@Lazy注解,探讨它们解决循环依赖的原理和优缺点。@RequiredArgsConstructor通过构造函数注入依赖,使代码更简洁;@Lazy则通过延迟Bean的初始化,打破创建顺序依赖。两者各有优势,需根据具体场景选择合适的方法。
84 4
|
3月前
|
Java 应用服务中间件 API
Vertx高并发理论原理以及对比SpringBoot
Vertx 是一个基于 Netty 的响应式工具包,不同于传统框架如 Spring,它的侵入性较小,甚至可在 Spring Boot 中使用。响应式编程(Reactive Programming)基于事件模式,通过事件流触发任务执行,其核心在于事件流 Stream。相比多线程异步,响应式编程能以更少线程完成更多任务,减少内存消耗与上下文切换开销,提高 CPU 利用率。Vertx 适用于高并发系统,如 IM 系统、高性能中间件及需要较少服务器支持大规模 WEB 应用的场景。随着 JDK 21 引入协程,未来 Tomcat 也将优化支持更高并发,降低响应式框架的必要性。
Vertx高并发理论原理以及对比SpringBoot