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