四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat

cnblogs 2024-08-31 08:09:00 阅读 75

四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat

@

目录

    <li>四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat
  • 1. 源码分析 Spring Boot是如何启动 Tomcat ,并支持访问 @Controller 的 Debug 流程分析
    • 1.1 源码分析: SpringApplication.run( ) 方法
  • 2. 自己编写实现 Spring Boot 底层机制【Tomcat启动分析 + Spring容器初始化 + Tomcat如何关联 Spring容器】
    • 2.1 实现任务阶段1:创建Tomcat 并启动
    • 2.2 实现任务阶段2:创建Spring容器
    • 2.3 实现任务阶段3:将Tomcat 和 Spring 容器关联,并启动Spring容器
  • 3. 最后:

1. 源码分析 Spring Boot是如何启动 Tomcat ,并支持访问 @Controller 的 Debug 流程分析

进行源码分析,自然是少不了,Debug 的。下面就让我们打上断点 ,Debug起来吧

1.1 源码分析: SpringApplication.run( ) 方法

<code>SpringApplication.run()

DeBug SpringApplication.run(MainApp.class, args); 看看 Spring Boot 是如何启动 Tomcat的

我们的Debug 目标:紧抓一条线,就是看到 tomcat 被启动的代码: 比如 tomcat.start()

  1. SpringApplication.java

在这里插入图片描述

在这里插入图片描述

<code>// 这里我们开始 Debug SpringApplication。run()

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {

return run(new Class[]{primarySource}, args);

}

    <li>SpringApplication.java

在这里插入图片描述

<code>

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {

return (new SpringApplication(primarySources)).run(args);

}

3.SpringApplication.java

在这里插入图片描述

在这里插入图片描述

<code> 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);

}

}

    <li>SpringApplication.java : 容器类型很多,会根据你的 this.webApplicationType 创建对应的容器

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

在这里插入图片描述

<code>

protected ConfigurableApplicationContext createApplicationContext() {

return this.applicationContextFactory.create(this.webApplicationType);

}

    <li>ApplicationContextFactory.java

    默认是进入这个分支 case SERVLET: 返回 new AnnotationConfigServletWebServerApplicationContext();

在这里插入图片描述

<code>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();

};

}

}

    <li>SpringApplication.java

在这里插入图片描述

在这里插入图片描述

<code> private void refreshContext(ConfigurableApplicationContext context) {

if (this.registerShutdownHook) {

shutdownHook.registerApplicationContext(context);

}

this.refresh(context); // 特别分析,真正执行相关任务

}

    <li>SpringApplication.java

在这里插入图片描述

<code>

protected void refresh(ConfigurableApplicationContext applicationContext) {

applicationContext.refresh();

}

    <li>Servlet 中的 ServletWebServerApplicationContext.java

在这里插入图片描述

<code> public final void refresh() throws BeansException, IllegalStateException {

try {

super.refresh(); // 特别分析这个方法

} catch (RuntimeException var3) {

WebServer webServer = this.webServer;

if (webServer != null) {

webServer.stop();

}

throw var3;

}

}

    <li>ApplicationContextFactory .java

在这里插入图片描述

<code>

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();

}

}

}

    <li>ServletWebServerApplicationContext.java

    在这里插入图片描述

    li>

<code>

protected void onRefresh() {

super.onRefresh();

try {

this.createWebServer(); 创建 webServer 可以理解成会创建指定 web服务器-tomcat

} catch (Throwable var2) {

throw new ApplicationContextException("Unable to start web server", var2);

}

}

    <li>ServletWebServerApplicationContext.java

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

<code> 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

在这里插入图片描述

<code> 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);

}

    <li>TomcatServletWebServerFactory.java

在这里插入图片描述

<code>

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {

return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());

}

    <li>TomcatWebServer.java

在这里插入图片描述

<code> 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(); // 分析这个方法

}

    <li>TomcatWebServer.java this.tomcat.start(); 初始化 Tomcat 服务器。

在这里插入图片描述

<code> 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 包。

在这里插入图片描述

<code><?xml version="1.0" encoding="UTF-8"?>code>

<project xmlns="http://maven.apache.org/POM/4.0.0"code>

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"code>

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">code>

<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 并启动

在这里插入图片描述

<code>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 对象。

在这里插入图片描述

<code>package com.rainbowsea.myspringboot.bean;

import org.springframework.stereotype.Controller;

@Controller // 加入到 ioc 容器当中管理

public class Monster {

}

bean 对象对应的 config 配置类

在这里插入图片描述

<code>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 处理业务请求的控制器

在这里插入图片描述

<code>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容器

在这里插入图片描述

<code>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("/");

}

}

在这里插入图片描述

<code>package com.rainbowsea.myspringboot;

public class MyMainApp {

public static void main(String[] args) {

// 启动MySpringBoot 项目/程序

MySpringApplication.run();

}

}

3. 最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。