热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

分析SpringBoot底层机制【Tomcat启动分析+Spring容器初始化+Tomcat如何关联Spring容器】

目录一.搭建SpringBoot底层机制开发环境1.pom.xml文件配置2.springboot主程序MainApp.java3.启动项目,然后我们准备开始思

目录

一.搭建 SpringBoot 底层机制开发环境

1.pom.xml文件配置

2.springboot主程序MainApp.java

3.启动项目,然后我们准备开始思考

4.开始思考

底层机制分析: 仍然是 我们实现 Spring 容器那一套机制 IO/文件扫描+注解+反射+ 集合+映射集合+映射

二.源码分析: SpringApplication.run()

重点

三.实现 SpringBoot 底层机制 【Tomcat 启动分析 + Spring 容器初始化+Tomcat 如何关联Spring 容器 】

1.实现任务阶段 1- 创建 Tomcat, 并启动

1.1说明: 创建 Tomcat, 并启动

1.2分析+代码实现

2.实现任务阶段 2- 创建 Spring 容器

2.1说明: 创建 Spring 容器

2.2 分析+代码实现

3.实现任务阶段 3- 将 Tomcat 和 Spring 容器关联, 并启动 Spring 容器

3.1 说明: 将 Tomcat 和 Spring 容器关联, 并启动 Spring 容器

3.2 分析+代码实现

3.3debug 一下, 看看是否进行 Spring 容器的初始化工作, 可以看到 ac.refresh() 会将




一.搭建 SpringBoot 底层机制开发环境


1.pom.xml文件配置

org.springframework.bootspring-boot-starter-parent2.5.3org.springframework.bootspring-boot-starter-weborg.apache.tomcattomcat-jasper8.5.75

2.springboot主程序MainApp.java

@SpringBootApplication
public class MainApp {public static void main(String[] args) {ConfigurableApplicationContext ioc =SpringApplication.run(Main.class, args);}

3.启动项目,然后我们准备开始思考

我们发现当我们启动项目的时候tomcat也会直接启动,底层到底发生了什么? 


4.开始思考

首先先建立一个Dog的文件

之后我们将这个文件config写出来

/*** Created with IntelliJ IDEA.** @Author: 海绵hong* @Date: 2022/11/19/22:55* @Description:标识标识的是一个配置类,充当Spring配置文件/容器的角色* 如果该配置类,如果在springboot扫描的包/子包,会被注入到容器中* 在该类中,可以通过这个注解@Bean来注入其他的主键*/
@Configuration
public class Config {/*** 1. 通过@Bean的方式, 将new出来的Bean对象, 放入到Spring容器* 2. 该bean在Spring容器的name/id 默认就是 方法名* 3. 通过方法名, 可以得到注入到spring容器中的dog对象**/@Beanpublic Dog dog(){return new Dog();}}


底层机制分析: 仍然是 我们实现 Spring 容器那一套机制 IO/文件扫描+注解+反射+ 集合+映射集合+映射


二.源码分析: SpringApplication.run()


1Debug SpringApplication.run(MainApp.class, args) 看看 SpringBoot 是如何启动 Tomcat .

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

package com.hong.springboot;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;/*** @author 韩顺平* @version 1.0*/
@SpringBootApplication
public class MainApp {public static void main(String[] args) {//启动springboot应用程序/项目//提出问题: 当我们执行run方法时,怎么就启动我们的内置的tomcat?//在分析run方法的底层机制的基础上,我们自己尝试实现ConfigurableApplicationContext ioc =SpringApplication.run(MainApp.class, args);/*** 这里我们开始Debug SpringApplication.run()* 1. SpringApplication.java* public static ConfigurableApplicationContext run(Class primarySource, String... args) {* return run(new Class[] { primarySource }, args);* }** 2.SpringApplication.java : 创建返回 ConfigurableApplicationContext对象* 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= createBootstrapContext();* ConfigurableApplicationContext cOntext= null;* configureHeadlessProperty();* SpringApplicationRunListeners listeners = getRunListeners(args);* listeners.starting(bootstrapContext, this.mainApplicationClass);* try {* ApplicationArguments applicatiOnArguments= new DefaultApplicationArguments(args);* ConfigurableEnvironment envirOnment= prepareEnvironment(listeners, bootstrapContext, applicationArguments);* configureIgnoreBeanInfo(environment);* Banner printedBanner = printBanner(environment);* cOntext= createApplicationContext(); //严重分析: 创建容器* context.setApplicationStartup(this.applicationStartup);* prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);* refreshContext(context); //严重分析: 刷新应用程序上下文,比如 初始化默认设置/注入相关Bean/启动tomcat* afterRefresh(context, applicationArguments);* stopWatch.stop();* if (this.logStartupInfo) {* new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);* }* listeners.started(context);* callRunners(context, applicationArguments);* }* catch (Throwable ex) {* handleRunFailure(context, ex, listeners);* throw new IllegalStateException(ex);* }** try {* listeners.running(context);* }* catch (Throwable ex) {* handleRunFailure(context, ex, null);* throw new IllegalStateException(ex);* }* return context;* }** 4. SpringApplication.java : 容器类型很多,会根据你的this.webApplicationType创建对应的容器* 默认 this.webApplicationType 是 SERVLET 也就是web容器/可以处理servlet* protected ConfigurableApplicationContext createApplicationContext() {* return this.applicationContextFactory.create(this.webApplicationType);* }** 5. ApplicationContextFactory.java** ApplicationContextFactory DEFAULT = (webApplicationType) -> {* try {* switch (webApplicationType) {* case SERVLET://默认是进入到这个分支 ,返回AnnotationConfigServletWebServerApplicationContext容器* return new AnnotationConfigServletWebServerApplicationContext();* case REACTIVE:* return new AnnotationConfigReactiveWebServerApplicationContext();* default:* return new AnnotationConfigApplicationContext();* }* }* catch (Exception ex) {* throw new IllegalStateException("Unable create a default ApplicationContext instance, "* + "you may need a custom ApplicationContextFactory", ex);* }* };** 6. SpringApplication.java* private void refreshContext(ConfigurableApplicationContext context) {* if (this.registerShutdownHook) {* shutdownHook.registerApplicationContext(context);* }* refresh(context); //严重分析,真正执行相关任务* }** 7. SpringApplication.java* protected void refresh(ConfigurableApplicationContext applicationContext) {* applicationContext.refresh();* }*** 8. ServletWebServerApplicationContext.java* @Override* public final void refresh() throws BeansException, IllegalStateException {* try {* super.refresh();//分析这个方法* }* catch (RuntimeException ex) {* WebServer webServer = this.webServer;* if (webServer != null) {* webServer.stop();* }* throw ex;* }* }** 9. AbstractApplicationContext.java** @Override* public void refresh() throws BeansException, IllegalStateException {* synchronized (this.startupShutdownMonitor) {* StartupStep cOntextRefresh= this.applicationStartup.start("spring.context.refresh");** // Prepare this context for refreshing.* prepareRefresh();** // Tell the subclass to refresh the internal bean factory.* ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();** // Prepare the bean factory for use in this context.* prepareBeanFactory(beanFactory);** try {* // Allows post-processing of the bean factory in context subclasses.* postProcessBeanFactory(beanFactory);** StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");* // Invoke factory processors registered as beans in the context.* invokeBeanFactoryPostProcessors(beanFactory);** // Register bean processors that intercept bean creation.* registerBeanPostProcessors(beanFactory);* beanPostProcess.end();** // Initialize message source for this context.* initMessageSource();** // Initialize event multicaster for this context.* initApplicationEventMulticaster();** // Initialize other special beans in specific context subclasses.* onRefresh(); //严重分析,当父类完成通用的工作后,再重新动态绑定机制回到子类** // Check for listener beans and register them.* registerListeners();** // Instantiate all remaining (non-lazy-init) singletons.* finishBeanFactoryInitialization(beanFactory);** // Last step: publish corresponding event.* finishRefresh();* }** catch (BeansException ex) {* if (logger.isWarnEnabled()) {* logger.warn("Exception encountered during context initialization - " +* "cancelling refresh attempt: " + ex);* }** // Destroy already created singletons to avoid dangling resources.* destroyBeans();** // Reset 'active' flag.* cancelRefresh(ex);** // Propagate exception to caller.* throw ex;* }** finally {* // Reset common introspection caches in Spring's core, since we* // might not ever need metadata for singleton beans anymore...* resetCommonCaches();* contextRefresh.end();* }* }* }* 10. ServletWebServerApplicationContext.java* @Override* protected void onRefresh() {* super.onRefresh();* try {* createWebServer();//看到胜利的曙光,创建webserver 可以理解成会创建指定web服务-Tomcat* }* catch (Throwable ex) {* throw new ApplicationContextException("Unable to start web server", ex);* } * }* 11. ServletWebServerApplicationContext.java** private void createWebServer() {* WebServer webServer = this.webServer;* ServletContext servletCOntext= getServletContext();* if (webServer == null && servletCOntext== null) {* StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");* ServletWebServerFactory factory = getWebServerFactory();* createWebServer.tag("factory", factory.getClass().toString());* this.webServer = factory.getWebServer(getSelfInitializer());//严重分析,使用TomcatServletWebServerFactory 创建一个TomcatWebServer* createWebServer.end();* getBeanFactory().registerSingleton("webServerGracefulShutdown",* new WebServerGracefulShutdownLifecycle(this.webServer));* getBeanFactory().registerSingleton("webServerStartStop",* new WebServerStartStopLifecycle(this, this.webServer));* }* else if (servletContext != null) {* try {* getSelfInitializer().onStartup(servletContext);* }* catch (ServletException ex) {* throw new ApplicationContextException("Cannot initialize servlet context", ex);* }* }* initPropertySources(); * }** 12. TomcatServletWebServerFactory.java 会创建Tomcat 并启动Tomcat** @Override* public WebServer getWebServer(ServletContextInitializer... initializers) {* if (this.disableMBeanRegistry) {* Registry.disableRegistry();* }* Tomcat tomcat = new Tomcat();//创建了Tomcat对象* File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");* //做了一系列的设置* tomcat.setBaseDir(baseDir.getAbsolutePath());** Connector cOnnector= new Connector(this.protocol);* connector.setThrowOnFailure(true);* tomcat.getService().addConnector(connector);* customizeConnector(connector);* tomcat.setConnector(connector);* tomcat.getHost().setAutoDeploy(false);* configureEngine(tomcat.getEngine());* for (Connector additionalConnector : this.additionalTomcatConnectors) {* tomcat.getService().addConnector(additionalConnector);* }* prepareContext(tomcat.getHost(), initializers);* return getTomcatWebServer(tomcat); //严重分析该方法.* }** 13. TomcatServletWebServerFactory.java , 这里做了校验创建 TomcatWebServer* protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {* return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());* }* 14. TomcatServletWebServerFactory.java* public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {* Assert.notNull(tomcat, "Tomcat Server must not be null");* this.tomcat = tomcat;* this.autoStart = autoStart;* this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;* initialize();//分析这个方法.* }* 15.TomcatServletWebServerFactory.java** private void initialize() throws WebServerException {* logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));* synchronized (this.monitor) {* try {* addInstanceIdToEngineName();** Context cOntext= findContext();* context.addLifecycleListener((event) -> {* if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {* // Remove service connectors so that protocol binding doesn't* // happen when the service is started.* removeServiceConnectors();* } * });** // Start the server to trigger initialization listeners* this.tomcat.start(); //启动Tomcat** // We can re-throw failure exception directly in the main thread* rethrowDeferredStartupExceptions();** try {* ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());* }* catch (NamingException ex) {* // Naming is not enabled. Continue* }** // Unlike Jetty, all Tomcat threads are daemon threads. We create a* // blocking non-daemon to stop immediate shutdown* startDaemonAwaitThread();* }* catch (Exception ex) {* stopSilently();* destroySilently();* throw new WebServerException("Unable to start embedded Tomcat", ex);* }* }* }*/System.out.println("hello ioc");}
}

重点

就是创建了一个容器,注入了相应的bean,启动了tomcat


三.实现 SpringBoot 底层机制 【Tomcat 启动分析 + Spring 容器初始化+Tomcat 如何关联Spring 容器 】


1.实现任务阶段 1- 创建 Tomcat, 并启动


1.1说明: 创建 Tomcat, 并启动


1.2分析+代码实现


● 代码实现        

1.修改pom.xml

org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-tomcatorg.apache.tomcat.embedtomcat-embed-core8.5.75

2.SpringApplication.java

public class HongSpringApplication {//这里我们会创建tomcat对象,并关联Spring容器, 并启动public static void run() {try {//创建Tomcat对象 HspTomcatTomcat tomcat = new Tomcat();//设置9090tomcat.setPort(9090);//启动tomcat.start();//等待请求接入System.out.println("======9090====等待请求=====");tomcat.getServer().await();} catch (Exception e) {e.printStackTrace();}}
}

3.MainApp.java

public class HongMainApp {public static void main(String[] args) {//启动HspSpringBoot项目/程序HspSpringApplication.run();}
}

 


2.实现任务阶段 2- 创建 Spring 容器


2.1说明: 创建 Spring 容器


2.2 分析+代码实现


代码实现

1.Monster.java , 做一个测试 Bean

public class Monster {
}

2.HiController.java, 作为 Controller

@RestController
public class HiController {@RequestMapping("/hi")public String hi() {return "hi,hong HiController";}
}

3.HongConfig.java , 作为 Spring 的配置文件.

/*** @author 海绵hong* @version 1.0* HspConfig:配置类-作为Spring的配置文件* 这里有一个问题,容器怎么知道要扫描哪些包? =>一会代码会体现** 在配置类可以指定要扫描包: @ComponentScan("com.hong.hongspringboot")*/
@Configuration
@ComponentScan("com.hong.hongspringboot")
public class HongConfig {//注入Bean - monster 对象到Spring容器.@Beanpublic Monster monster() {return new Monster();}
}

4.WebApplicationInitializer.java , 作为 Spring 的容器.

/*** @author 海绵hong* @version 1.0* Initializer: 初始化器*//*** 解读* 1. 创建我们的Spring 容器* 2. 加载/关联Spring容器的配置-按照注解的方式* 3. 完成Spring容器配置的bean的创建, 依赖注入* 4. 创建前端控制器 DispatcherServlet , 并让其持有Spring容器* 5. 当DispatcherServlet 持有容器, 就可以进行分发映射, 请小伙伴回忆我们实现SpringMVC底层机制* 6. 这里onStartup 是Tomcat调用, 并把ServletContext 对象传入*/
public class HspWebApplicationInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {System.out.println("startup ....");//加载Spring web application cOnfiguration=> 容器//自己 写过 HongSpringApplicationContextAnnotationConfigWebApplicationContext ac =new AnnotationConfigWebApplicationContext();//在ac中注册 HongConfig.class 配置类ac.register(HongConfig.class);ac.refresh();//完成bean的创建和配置//1. 创建注册非常重要的前端控制器 DispatcherServlet//2. 让DispatcherServlet 持有容器//3. 这样就可以进行映射分发, 回忆一下SpringMvc机制[自己实现过]//HongDispatcherServletDispatcherServlet dispatcherServlet = new DispatcherServlet(ac);//返回了ServletRegistration.Dynamic对象ServletRegistration.Dynamic registration =servletContext.addServlet("app", dispatcherServlet);//当tomcat启动时,加载 dispatcherServletregistration.setLoadOnStartup(1);//拦截请求,并进行分发处理//这里在提示 / 和 /* => 在讲解 java web , 自己去看看.registration.addMapping("/");}
}

3.实现任务阶段 3- Tomcat Spring 容器关联, 并启动 Spring 容器


3.1 说明: Tomcat Spring 容器关联, 并启动 Spring 容器


3.2 分析+代码实现


代码实现

1.SpringApplication.java

public class HongSpringApplication {//这里我们会创建tomcat对象,并关联Spring容器, 并启动public static void run() {try {//创建Tomcat对象 HspTomcatTomcat tomcat = new Tomcat();//1. 让tomcat可以将请求转发到spring web容器,因此需要进行关联//2. "/hong" 就是我们的项目的 application context , 就是我们原来配置tomcat时,指定的application context//3. "D:\\hspedu_springboot\\hsp-springboot" 指定项目的目录tomcat.addWebapp("/hong","D:\\hong_springboot\\hong-springboot");//设置9090tomcat.setPort(9090);//启动tomcat.start();//等待请求接入System.out.println("======9090====等待请求=====");tomcat.getServer().await();} catch (Exception e) {e.printStackTrace();}}
}

3.3debug 一下, 看看是否进行 Spring 容器的初始化工作, 可以看到 ac.refresh() 会将


HspConfig.class 中配置 Bean 实例化装入到容器中.

 

 

结果:


推荐阅读
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • (三)多表代码生成的实现方法
    本文介绍了一种实现多表代码生成的方法,使用了java代码和org.jeecg框架中的相关类和接口。通过设置主表配置,可以生成父子表的数据模型。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
  • 使用eclipse创建一个Java项目的步骤
    本文介绍了使用eclipse创建一个Java项目的步骤,包括启动eclipse、选择New Project命令、在对话框中输入项目名称等。同时还介绍了Java Settings对话框中的一些选项,以及如何修改Java程序的输出目录。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有