四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat
四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat
@
1. 源码分析 Spring Boot是如何启动 Tomcat ,并支持访问 @Controller 的 Debug 流程分析
进行源码分析,自然是少不了,Debug 的。下面就让我们打上断点 ,Debug起来吧
1.1 源码分析: SpringApplication.run( ) 方法
SpringApplication.run()
DeBug SpringApplication.run(MainApp.class, args); 看看 Spring Boot 是如何启动 Tomcat的
我们的Debug 目标:紧抓一条线,就是看到 tomcat 被启动的代码: 比如
tomcat.start()
- SpringApplication.java
// 这里我们开始 Debug SpringApplication。run()
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
- SpringApplication.java
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
3.SpringApplication.java
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);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext(); // 特别分析: 创建容器
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context); // 特别分析:刷新应用程序上下文,比如:初始化默认设置/注入相关Bean/启动 tomcat
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);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
- SpringApplication.java : 容器类型很多,会根据你的 this.webApplicationType 创建对应的容器
默认 this.webApplicationType 是 servlet 也就是 web 容器/处理的 servlet
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
- ApplicationContextFactory.java
默认是进入这个分支 case SERVLET: 返回 new AnnotationConfigServletWebServerApplicationContext();
public interface ApplicationContextFactory {
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch(webApplicationType) {
case SERVLET: // 默认是进入这个分支
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);
}
};
ConfigurableApplicationContext create(WebApplicationType webApplicationType);
static ApplicationContextFactory ofContextClass(Class<? extends ConfigurableApplicationContext> contextClass) {
return of(() -> {
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
});
}
static ApplicationContextFactory of(Supplier<ConfigurableApplicationContext> supplier) {
return (webApplicationType) -> {
return (ConfigurableApplicationContext)supplier.get();
};
}
}
- SpringApplication.java
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
this.refresh(context); // 特别分析,真正执行相关任务
}
- SpringApplication.java
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
- Servlet 中的 ServletWebServerApplicationContext.java
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh(); // 特别分析这个方法
} catch (RuntimeException var3) {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.stop();
}
throw var3;
}
}
- ApplicationContextFactory .java
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh(); // 特别分析,当父类完成通用的工作后,再重新动态绑定机制回到
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var10) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
}
this.destroyBeans();
this.cancelRefresh(var10);
throw var10;
} finally {
this.resetCommonCaches();
contextRefresh.end();
}
}
}
- ServletWebServerApplicationContext.java
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer(); 创建 webServer 可以理解成会创建指定 web服务器-tomcat
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
- ServletWebServerApplicationContext.java
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = this.getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer
()}); // 特别分析,使用 TomcatServletWebServerFactory 创建一个TomcatWEbServer
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();
}
12.TomcatServletWebServerFactory.java 会创建Tomcat 并启动 Tomcat
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);
}
- TomcatServletWebServerFactory.java
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
}
- TomcatWebServer.java
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
this.monitor = new Object();
this.serviceConnectors = new HashMap();
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null;
this.initialize(); // 分析这个方法
}
- TomcatWebServer.java this.tomcat.start(); 初始化 Tomcat 服务器。
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
synchronized(this.monitor) {
try {
this.addInstanceIdToEngineName();
Context context = this.findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && "start".equals(event.getType())) {
this.removeServiceConnectors();
}
});
this.tomcat.start(); // ********* 启动 Tomcat
this.rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
} catch (NamingException var5) {
}
this.startDaemonAwaitThread();
} catch (Exception var6) {
this.stopSilently();
this.destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", var6);
}
}
}
2. 自己编写实现 Spring Boot 底层机制【Tomcat启动分析 + Spring容器初始化 + Tomcat如何关联 Spring容器】
**Spring Boot 注入打 ioc 容器:底层机制:仍然是:我们实现Spring 容器那一套机制
IO/文件扫描+注解+反射+集合+映射
** 。
依靠 Maven 在 pom.xml 文件中配置相应所需的 jar
包。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rainbowsea</groupId>
<artifactId>mySpringBoot</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 导入SpringBoot 父工程-规定写法-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
</parent>
<!-- 导入web项目场景启动器:会自动导入和web开发相关的jar包所有依赖【库/jar】-->
<!-- 后面还会在说明spring-boot-starter-web 到底引入哪些相关依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--
因为我们自己要创建 Tomcat 对象,并启动,
因此我们先排除 内嵌的 spring-boot-starter-tomcat
-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 我们指定tomcat版本,引入 tomcat 依赖/库-->
<!--
1. 使用指定的tomcat 8.5.75 ,请小伙伴也引入这个版本
2. 如果我们引入自己指定的tomcat,一定要记住把前面spring-boot-stater-tomcat 排除
3. 如果你不排除,会出现 GenericServlet Not Found错误
-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.75</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>8.5.75</version>
</dependency>
</dependencies>
</project>
2.1 实现任务阶段1:创建Tomcat 并启动
package com.rainbowsea.myspringboot;
import org.apache.catalina.startup.Tomcat;
public class MySpringApplication {
// 这里我们会创建一个tomcat对象,并关联Spring容器,并启动
public static void run() {
try {
// 创建tomcat对象
Tomcat tomcat = new Tomcat();
// 1. 让tomcat 可以将请求转发到spring web 容器,因此需要进行关联
// 2. “/myspboot 就是我们的项目的 application context ,就是我们原来配置tomcat时,指定application
tomcat.addWebapp("/app","E:\\Java\\SpringBoot\\quickstart\\mySpringBoot");
//
// 设置监视端口为 9090
tomcat.setPort(8080);
// 启动 Tomcat
tomcat.start();
// 等待请求接入
System.out.println("===9090===等待请求=========");
tomcat.getServer().await();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
2.2 实现任务阶段2:创建Spring容器
bean 对象。
package com.rainbowsea.myspringboot.bean;
import org.springframework.stereotype.Controller;
@Controller // 加入到 ioc 容器当中管理
public class Monster {
}
bean 对象对应的 config 配置类
package com.rainbowsea.myspringboot.config;
import com.rainbowsea.myspringboot.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/*
这里有一个问题,容器怎么知道要扫描哪些包?
在配置类可以指定要扫描包: @ComponentScan("com.rainbowsea.myspringboot")
MyConfig 配置类-作为Spring的配置文件
*/
@ComponentScan("com.rainbowsea.myspringboot.bean")
@Configuration // 标注: 设置类
public class MyConfig {
// 注入Bean - monster 对象到 Spring 容器
@Bean
public Monster monster() {
return new Monster();
}
}
controller 处理业务请求的控制器
package com.rainbowsea.myspringboot.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // @Controller + @ResponseBod
public class MyHiController {
@RequestMapping("/myhi") // 设置请求映射路径
public String hi() {
return "hi my MyHiController ";
}
}
2.3 实现任务阶段3:将Tomcat 和 Spring 容器关联,并启动Spring容器
package com.rainbowsea.myspringboot;
import com.rainbowsea.myspringboot.config.MyConfig;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
/**
* 解读:
* 1. 创建我们的Spring容器
* 2. 加载/关联Spring容器的配置-按照注解的方式
* 3. 完成Spring容器配置的bean的创建,依赖注入
* 4. 创建前端控制器 DispatcherServlet,并让其持有Spring容器
* 5. 当DispatcherServlet 持有容器,就可以进行分发映射,请小伙伴回忆我们实现SpringMVC
* 6. 这里onStartup 是Tomcat 调用,并把ServletContext 对象传入
*/
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("startup...");
// 加载Spring web application configuration => 容器
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(MyConfig.class);
ac.refresh(); // 完成bean的创建和配置
// 1. 创建注册非常重要的前端控制器 当DispatcherServlet
// 2. 让 DispatcherServlet 持有容器
// 3. 这样就可以进行映射分发,回忆一下 SpringMVC 机制(自己实现过)
DispatcherServlet dispatcherServlet = new DispatcherServlet(ac);
// 返回 ServletRegistration.Dynamic 对象
ServletRegistration.Dynamic registration = servletContext.addServlet("app", dispatcherServlet);
// tomcat 启动时,加载 dispatcherServlet
registration.setLoadOnStartup(1);
// 拦截请求,并进行分发处理
// 这里老师在提示 "/" 和 “/*” 在老师讲解 java web
registration.addMapping("/");
}
}
package com.rainbowsea.myspringboot;
public class MyMainApp {
public static void main(String[] args) {
// 启动MySpringBoot 项目/程序
MySpringApplication.run();
}
}
3. 最后:
“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”
四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat的更多相关文章
- spring boot 2.0 源码分析(四)
在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...
- Spring Boot 自动配置 源码分析
Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...
- spring boot 2.0 源码分析(一)
在学习spring boot 2.0源码之前,我们先利用spring initializr快速地创建一个基本的简单的示例: 1.先从创建示例中的main函数开始读起: package com.exam ...
- 43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】
[视频&交流平台] àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm ...
- (43). Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】
在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...
- Spring Boot(七)扩展分析
前面的章节在分析SpringBoot启动过程中,我们发现SpringBoot使用Spring框架提供的SpringFactoriesLoader这个类,实现检索META-INF/spring.fact ...
- 【spring boot】6.idea下springboot打包成jar包和war包,并且可以在外部tomcat下运行访问到
接着上一章走呗:http://www.cnblogs.com/sxdcgaq8080/p/7712874.html 然后声明一点,下面打包的过程中,scope一直都是使用默认的范围 <!--用于 ...
- spring boot 2.0 源码分析(五)
在上一篇文章中我们详细分析了spring boot是如何准备上下文环境的,今天我们来看一下run函数剩余的内容.还是先把run函数贴出来: /** * Run the Spring applicati ...
- spring boot 2.0 源码分析(二)
在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...
- spring boot 2.0 源码分析(三)
通过上一章的源码分析,我们知道了spring boot里面的listeners到底是什么(META-INF/spring.factories定义的资源的实例),以及它是创建和启动的,今天我们继续深入分 ...
随机推荐
- .Net Core 访问 appsettings.json
1.添加 NuGet 包 Microsoft.Extensions.Configuration 2.通过注入获取 Configuration 注意:注入获取的必须提前在 StartUp 里面提前注册 ...
- Python 潮流周刊#59:Polars 1.0 发布了,PyCon US 2024 演讲视频也发布了(摘要)
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...
- NewstarCTF 2023 Misc
NewStarCTF 2023 Misc week1 的 misc 请移步上一篇 NewStarCTF WEEK2 新建Word文档 直接复制出不来,改后缀为zip,document.xml得到内容 ...
- Redis学习篇
Redis 能用来做什么? 01 缓存 Redis 的最常用的用例是缓存,以加快网络应用的速度.在这种用例中,Redis 将经常请求的数据存储在内存中.它允许网络服务器频繁访问的数据.这就减少了数据库 ...
- JS中this的几种指向
这些this的指向,是当我们调用函数的时候确定的.调用方式的不同决定了this的指向不同一般指向我们的调用者.普通函数调用 window.定时器函数 wind ...
- [oeasy]python0106 七段数码管_显示字母_BP机
七位数码管进化 回忆上次内容 上次回顾了 7-seg 七位数码管 可以显示数字 甚至是十六进制数字 添加图片注释,不超过 140 字(可选) 能否让 七位数码管 将26个字母 全部都显 ...
- 2023 NOIP 游记
\(\text{Day -INF}\) 提高 \(135\) 卡线进 \(\text{NOIP}\). 集训两天成绩:\(50 \to 135\). \(\text{Day 1}\) 开赛 \(13\ ...
- Python 按规则解析并替换字符串中的变量及函数
按规则解析并替换字符串中的变量及函数 需求 1.按照一定规则解析字符串中的函数.变量表达式,并替换这些表达式.这些函数表达式可能包含其它函数表达式,即支持函数嵌套 2.函数表达式格式:${ __函数名 ...
- python lambda 三元表达式
python lambda 三元表达式 python中的lambda函数用法 通常定义的函数 def sum(x,y): return x+y print(sum(4,6)) 用lambda来实现: ...
- 平衡树之Splay树详解
认识 Splay树,BST(二叉搜索树)的一种,整体效率很高,平摊操作次数为\(O(log_2n)\),也就是说,在一棵有n个节点的BST上做M次Splay操作,时间复杂度为\(O(Mlog_2n)\ ...