四,分析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. DarkHole_1靶机渗透流程

    VulnHub_DarkHole1靶机渗透流程 注意:部署时,靶机的网络连接模式必须和kali一致,让靶机跟kali处于同一网段,这用kali才能扫出靶机的主机 1. 信息收集 1.1 探测IP 使用 ...

  2. yb课堂 搭建node环境和npm安装 《二十六》

    搭建node环境和npm安装 什么是NodeJS? Node.js就是运行在服务端得JavaScript 什么是npm? nodejs的包管理工具,可以下载使用公共仓库的包,类似maven包安装分为本 ...

  3. Dubbo依赖

    项目依赖 Dubbo依赖 <!--Dubbo依赖--> <dependency> <groupId>com.alibaba</groupId> < ...

  4. 解锁网络无限可能:揭秘微软工程师力作——付费代理IP池深度改造与实战部署指南

    基于付费代理的代理IP池 项目来源 此项目为微软某个工程师构建的代理IP池,我对此进行了改造.可以用于生产环境中的爬虫项目 阅读前建议 阅读我之前发布的爬虫基础的文章,了解代理如何获取.使用等. 分为 ...

  5. [oeasy]python0036_牛说_cowsay_小动物说话_asciiart_figlet_lolcat_管道(祝大家新年快乐~)

    ​ 牛说(cowsay) 回忆上次内容 上次我们研究了shell脚本的编程 并且在shell中实现了 循环语句 延迟命令 清屏命令 python命令 figlet命令 ​ 编辑 还能整点什么呢? 还想 ...

  6. Java 网络编程(TCP编程 和 UDP编程)

    1. Java 网络编程(TCP编程 和 UDP编程) @ 目录 1. Java 网络编程(TCP编程 和 UDP编程) 2. 网络编程的概念 3. IP 地址 3.1 IP地址相关的:域名与DNS ...

  7. CF1359A 题解

    洛谷链接&CF 链接 题目简述 共有 \(T\) 组数据. 对于每组数据给出 \(n,m,k\),表示 \(k\) 名玩家打牌,共 \(n\) 张牌,\(m\) 张王,保证 \(k \mid ...

  8. Docker 使用Docker创建MySQL容器

    使用Docker创建MySQL容器 实践环境 Docker version 20.10.5 MySQL5.7 Centos 7.8 创建步骤 1.拉取MySQL镜像 docker pull mysql ...

  9. 机器学习:详解什么是端到端的深度学习?(What is end-to-end deep learning?)

    什么是端到端的深度学习? 深度学习中最令人振奋的最新动态之一就是端到端深度学习的兴起,那么端到端学习到底是什么呢?简而言之,以前有一些数据处理系统或者学习系统,它们需要多个阶段的处理.那么端到端深度学 ...

  10. git 提交备注规范

    git 提交规范commit message = subject + :+ 空格 + message 主体 例如:feat:增加用户注册功能 常见的 subject 种类以及含义如下: feat: 新 ...