四,分析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()

  1. SpringApplication.java

// 这里我们开始 Debug SpringApplication。run()
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
  1. 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);
}
}
  1. SpringApplication.java : 容器类型很多,会根据你的 this.webApplicationType 创建对应的容器

    默认 this.webApplicationType 是 servlet 也就是 web 容器/处理的 servlet


protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
  1. 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();
};
}
}
  1. SpringApplication.java

    private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
this.refresh(context); // 特别分析,真正执行相关任务
}
  1. SpringApplication.java


protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
  1. 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;
}
}
  1. 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();
} }
}
  1. 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);
}
}
  1. 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);
}
  1. TomcatServletWebServerFactory.java


protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
}
  1. 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(); // 分析这个方法
}
  1. 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的更多相关文章

  1. spring boot 2.0 源码分析(四)

    在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...

  2. Spring Boot 自动配置 源码分析

    Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...

  3. spring boot 2.0 源码分析(一)

    在学习spring boot 2.0源码之前,我们先利用spring initializr快速地创建一个基本的简单的示例: 1.先从创建示例中的main函数开始读起: package com.exam ...

  4. 43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

    [视频&交流平台] àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm ...

  5. (43). Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

    在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...

  6. Spring Boot(七)扩展分析

    前面的章节在分析SpringBoot启动过程中,我们发现SpringBoot使用Spring框架提供的SpringFactoriesLoader这个类,实现检索META-INF/spring.fact ...

  7. 【spring boot】6.idea下springboot打包成jar包和war包,并且可以在外部tomcat下运行访问到

    接着上一章走呗:http://www.cnblogs.com/sxdcgaq8080/p/7712874.html 然后声明一点,下面打包的过程中,scope一直都是使用默认的范围 <!--用于 ...

  8. spring boot 2.0 源码分析(五)

    在上一篇文章中我们详细分析了spring boot是如何准备上下文环境的,今天我们来看一下run函数剩余的内容.还是先把run函数贴出来: /** * Run the Spring applicati ...

  9. spring boot 2.0 源码分析(二)

    在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...

  10. spring boot 2.0 源码分析(三)

    通过上一章的源码分析,我们知道了spring boot里面的listeners到底是什么(META-INF/spring.factories定义的资源的实例),以及它是创建和启动的,今天我们继续深入分 ...

随机推荐

  1. .Net Core 访问 appsettings.json

    1.添加 NuGet 包 Microsoft.Extensions.Configuration 2.通过注入获取 Configuration 注意:注入获取的必须提前在 StartUp 里面提前注册 ...

  2. Python 潮流周刊#59:Polars 1.0 发布了,PyCon US 2024 演讲视频也发布了(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  3. NewstarCTF 2023 Misc

    NewStarCTF 2023 Misc week1 的 misc 请移步上一篇 NewStarCTF WEEK2 新建Word文档 直接复制出不来,改后缀为zip,document.xml得到内容 ...

  4. Redis学习篇

    Redis 能用来做什么? 01 缓存 Redis 的最常用的用例是缓存,以加快网络应用的速度.在这种用例中,Redis 将经常请求的数据存储在内存中.它允许网络服务器频繁访问的数据.这就减少了数据库 ...

  5. JS中this的几种指向

    这些this的指向,是当我们调用函数的时候确定的.调用方式的不同决定了this的指向不同一般指向我们的调用者.普通函数调用          window.定时器函数             wind ...

  6. [oeasy]python0106 七段数码管_显示字母_BP机

    七位数码管进化 回忆上次内容 上次回顾了 7-seg 七位数码管 可以显示数字 甚至是十六进制数字   ​   添加图片注释,不超过 140 字(可选)   能否让 七位数码管 将26个字母 全部都显 ...

  7. 2023 NOIP 游记

    \(\text{Day -INF}\) 提高 \(135\) 卡线进 \(\text{NOIP}\). 集训两天成绩:\(50 \to 135\). \(\text{Day 1}\) 开赛 \(13\ ...

  8. Python 按规则解析并替换字符串中的变量及函数

    按规则解析并替换字符串中的变量及函数 需求 1.按照一定规则解析字符串中的函数.变量表达式,并替换这些表达式.这些函数表达式可能包含其它函数表达式,即支持函数嵌套 2.函数表达式格式:${ __函数名 ...

  9. python lambda 三元表达式

    python lambda 三元表达式 python中的lambda函数用法 通常定义的函数 def sum(x,y): return x+y print(sum(4,6)) 用lambda来实现: ...

  10. 平衡树之Splay树详解

    认识 Splay树,BST(二叉搜索树)的一种,整体效率很高,平摊操作次数为\(O(log_2n)\),也就是说,在一棵有n个节点的BST上做M次Splay操作,时间复杂度为\(O(Mlog_2n)\ ...