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


    目录

    一.搭建 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文件配置

    1. <parent>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-parentartifactId>
    4. <version>2.5.3version>
    5. parent>
    6. <dependencies>
    7. <dependency>
    8. <groupId>org.springframework.bootgroupId>
    9. <artifactId>spring-boot-starter-webartifactId>
    10. dependency>
    11. <dependency>
    12. <groupId>org.apache.tomcatgroupId>
    13. <artifactId>tomcat-jasperartifactId>
    14. <version>8.5.75version>
    15. dependency>
    16. dependencies>

    2.springboot主程序MainApp.java

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

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

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

    4.开始思考

    首先先建立一个Dog的文件

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

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

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

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

    1 Debug SpringApplication . run ( MainApp . class , args) 看看 SpringBoot 是如何启动 Tomcat .
    2 、我们的 Debug 目标 : 紧抓一条线 , 就是看到 tomcat 被启动的代码 . 比如 tomcat.start()
    1. package com.hong.springboot;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. import org.springframework.context.ConfigurableApplicationContext;
    5. @SpringBootApplication
    6. public class MainApp {
    7. public static void main(String[] args) {
    8. //启动springboot应用程序/项目
    9. //提出问题: 当我们执行run方法时,怎么就启动我们的内置的tomcat?
    10. //在分析run方法的底层机制的基础上,我们自己尝试实现
    11. ConfigurableApplicationContext ioc =
    12. SpringApplication.run(MainApp.class, args);
    13. /**
    14. * 这里我们开始Debug SpringApplication.run()
    15. * 1. SpringApplication.java
    16. * public static ConfigurableApplicationContext run(Class primarySource, String... args) {
    17. * return run(new Class[] { primarySource }, args);
    18. * }
    19. *
    20. * 2.SpringApplication.java : 创建返回 ConfigurableApplicationContext对象
    21. * public static ConfigurableApplicationContext run(Class[] primarySources, String[] args) {
    22. * return new SpringApplication(primarySources).run(args);
    23. * }
    24. *
    25. * 3. SpringApplication.java
    26. *
    27. * public ConfigurableApplicationContext run(String... args) {
    28. * StopWatch stopWatch = new StopWatch();
    29. * stopWatch.start();
    30. * DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    31. * ConfigurableApplicationContext context = null;
    32. * configureHeadlessProperty();
    33. * SpringApplicationRunListeners listeners = getRunListeners(args);
    34. * listeners.starting(bootstrapContext, this.mainApplicationClass);
    35. * try {
    36. * ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    37. * ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
    38. * configureIgnoreBeanInfo(environment);
    39. * Banner printedBanner = printBanner(environment);
    40. * context = createApplicationContext(); //严重分析: 创建容器
    41. * context.setApplicationStartup(this.applicationStartup);
    42. * prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
    43. * refreshContext(context); //严重分析: 刷新应用程序上下文,比如 初始化默认设置/注入相关Bean/启动tomcat
    44. * afterRefresh(context, applicationArguments);
    45. * stopWatch.stop();
    46. * if (this.logStartupInfo) {
    47. * new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    48. * }
    49. * listeners.started(context);
    50. * callRunners(context, applicationArguments);* }
    51. * catch (Throwable ex) {
    52. * handleRunFailure(context, ex, listeners);
    53. * throw new IllegalStateException(ex);
    54. * }
    55. *
    56. * try {
    57. * listeners.running(context);
    58. * }
    59. * catch (Throwable ex) {
    60. * handleRunFailure(context, ex, null);
    61. * throw new IllegalStateException(ex);
    62. * }
    63. * return context;
    64. * }
    65. *
    66. * 4. SpringApplication.java : 容器类型很多,会根据你的this.webApplicationType创建对应的容器
    67. * 默认 this.webApplicationType 是 SERVLET 也就是web容器/可以处理servlet
    68. * protected ConfigurableApplicationContext createApplicationContext() {
    69. * return this.applicationContextFactory.create(this.webApplicationType);
    70. * }
    71. *
    72. * 5. ApplicationContextFactory.java
    73. *
    74. * ApplicationContextFactory DEFAULT = (webApplicationType) -> {
    75. * try {
    76. * switch (webApplicationType) {
    77. * case SERVLET://默认是进入到这个分支 ,返回AnnotationConfigServletWebServerApplicationContext容器
    78. * return new AnnotationConfigServletWebServerApplicationContext();
    79. * case REACTIVE:
    80. * return new AnnotationConfigReactiveWebServerApplicationContext();
    81. * default:
    82. * return new AnnotationConfigApplicationContext();
    83. * }* }
    84. * catch (Exception ex) {
    85. * throw new IllegalStateException("Unable create a default ApplicationContext instance, "
    86. * + "you may need a custom ApplicationContextFactory", ex);
    87. * }
    88. * };
    89. *
    90. * 6. SpringApplication.java
    91. * private void refreshContext(ConfigurableApplicationContext context) {
    92. * if (this.registerShutdownHook) {
    93. * shutdownHook.registerApplicationContext(context);
    94. * }
    95. * refresh(context); //严重分析,真正执行相关任务
    96. * }
    97. *
    98. * 7. SpringApplication.java
    99. * protected void refresh(ConfigurableApplicationContext applicationContext) {
    100. * applicationContext.refresh();
    101. * }
    102. *
    103. *
    104. * 8. ServletWebServerApplicationContext.java
    105. * @Override
    106. * public final void refresh() throws BeansException, IllegalStateException {
    107. * try {
    108. * super.refresh();//分析这个方法
    109. * }
    110. * catch (RuntimeException ex) {
    111. * WebServer webServer = this.webServer;
    112. * if (webServer != null) {
    113. * webServer.stop();
    114. * }
    115. * throw ex;
    116. * }
    117. * }
    118. *
    119. * 9. AbstractApplicationContext.java
    120. *
    121. * @Override
    122. * public void refresh() throws BeansException, IllegalStateException {
    123. * synchronized (this.startupShutdownMonitor) {
    124. * StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
    125. *
    126. * // Prepare this context for refreshing.
    127. * prepareRefresh();
    128. *
    129. * // Tell the subclass to refresh the internal bean factory.
    130. * ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    131. *
    132. * // Prepare the bean factory for use in this context.
    133. * prepareBeanFactory(beanFactory);
    134. *
    135. * try {
    136. * // Allows post-processing of the bean factory in context subclasses.
    137. * postProcessBeanFactory(beanFactory);
    138. *
    139. * StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
    140. * // Invoke factory processors registered as beans in the context.
    141. * invokeBeanFactoryPostProcessors(beanFactory);
    142. *
    143. * // Register bean processors that intercept bean creation.
    144. * registerBeanPostProcessors(beanFactory);
    145. * beanPostProcess.end();
    146. *
    147. * // Initialize message source for this context.
    148. * initMessageSource();
    149. *
    150. * // Initialize event multicaster for this context.
    151. * initApplicationEventMulticaster();
    152. *
    153. * // Initialize other special beans in specific context subclasses.
    154. * onRefresh(); //严重分析,当父类完成通用的工作后,再重新动态绑定机制回到子类
    155. *
    156. * // Check for listener beans and register them.
    157. * registerListeners();
    158. *
    159. * // Instantiate all remaining (non-lazy-init) singletons.
    160. * finishBeanFactoryInitialization(beanFactory);
    161. *
    162. * // Last step: publish corresponding event.
    163. * finishRefresh();
    164. * }
    165. *
    166. * catch (BeansException ex) {
    167. * if (logger.isWarnEnabled()) {
    168. * logger.warn("Exception encountered during context initialization - " +
    169. * "cancelling refresh attempt: " + ex);
    170. * }
    171. *
    172. * // Destroy already created singletons to avoid dangling resources.
    173. * destroyBeans();
    174. *
    175. * // Reset 'active' flag.
    176. * cancelRefresh(ex);
    177. *
    178. * // Propagate exception to caller.
    179. * throw ex;
    180. * }
    181. *
    182. * finally {
    183. * // Reset common introspection caches in Spring's core, since we
    184. * // might not ever need metadata for singleton beans anymore...
    185. * resetCommonCaches();
    186. * contextRefresh.end();
    187. * }
    188. * }
    189. * }
    190. * 10. ServletWebServerApplicationContext.java
    191. * @Override
    192. * protected void onRefresh() {
    193. * super.onRefresh();
    194. * try {
    195. * createWebServer();//看到胜利的曙光,创建webserver 可以理解成会创建指定web服务-Tomcat
    196. * }
    197. * catch (Throwable ex) {
    198. * throw new ApplicationContextException("Unable to start web server", ex);
    199. * } * }
    200. * 11. ServletWebServerApplicationContext.java
    201. *
    202. * private void createWebServer() {
    203. * WebServer webServer = this.webServer;
    204. * ServletContext servletContext = getServletContext();
    205. * if (webServer == null && servletContext == null) {
    206. * StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
    207. * ServletWebServerFactory factory = getWebServerFactory();
    208. * createWebServer.tag("factory", factory.getClass().toString());
    209. * this.webServer = factory.getWebServer(getSelfInitializer());//严重分析,使用TomcatServletWebServerFactory 创建一个TomcatWebServer
    210. * createWebServer.end();
    211. * getBeanFactory().registerSingleton("webServerGracefulShutdown",
    212. * new WebServerGracefulShutdownLifecycle(this.webServer));
    213. * getBeanFactory().registerSingleton("webServerStartStop",
    214. * new WebServerStartStopLifecycle(this, this.webServer));
    215. * }
    216. * else if (servletContext != null) {
    217. * try {
    218. * getSelfInitializer().onStartup(servletContext);
    219. * }
    220. * catch (ServletException ex) {
    221. * throw new ApplicationContextException("Cannot initialize servlet context", ex);
    222. * }
    223. * }
    224. * initPropertySources(); * }
    225. *
    226. * 12. TomcatServletWebServerFactory.java 会创建Tomcat 并启动Tomcat
    227. *
    228. * @Override
    229. * public WebServer getWebServer(ServletContextInitializer... initializers) {
    230. * if (this.disableMBeanRegistry) {
    231. * Registry.disableRegistry();
    232. * }
    233. * Tomcat tomcat = new Tomcat();//创建了Tomcat对象
    234. * File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    235. * //做了一系列的设置
    236. * tomcat.setBaseDir(baseDir.getAbsolutePath());
    237. *
    238. * Connector connector = new Connector(this.protocol);
    239. * connector.setThrowOnFailure(true);
    240. * tomcat.getService().addConnector(connector);
    241. * customizeConnector(connector);
    242. * tomcat.setConnector(connector);
    243. * tomcat.getHost().setAutoDeploy(false);
    244. * configureEngine(tomcat.getEngine());
    245. * for (Connector additionalConnector : this.additionalTomcatConnectors) {
    246. * tomcat.getService().addConnector(additionalConnector);
    247. * }
    248. * prepareContext(tomcat.getHost(), initializers);
    249. * return getTomcatWebServer(tomcat); //严重分析该方法.
    250. * }
    251. *
    252. * 13. TomcatServletWebServerFactory.java , 这里做了校验创建 TomcatWebServer
    253. * protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    254. * return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
    255. * }
    256. * 14. TomcatServletWebServerFactory.java
    257. * public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
    258. * Assert.notNull(tomcat, "Tomcat Server must not be null");
    259. * this.tomcat = tomcat;
    260. * this.autoStart = autoStart;
    261. * this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
    262. * initialize();//分析这个方法.
    263. * }
    264. * 15.TomcatServletWebServerFactory.java
    265. *
    266. * private void initialize() throws WebServerException {
    267. * logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    268. * synchronized (this.monitor) {
    269. * try {
    270. * addInstanceIdToEngineName();
    271. *
    272. * Context context = findContext();
    273. * context.addLifecycleListener((event) -> {
    274. * if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
    275. * // Remove service connectors so that protocol binding doesn't
    276. * // happen when the service is started.
    277. * removeServiceConnectors();
    278. * } * });
    279. *
    280. * // Start the server to trigger initialization listeners
    281. * this.tomcat.start(); //启动Tomcat
    282. *
    283. * // We can re-throw failure exception directly in the main thread
    284. * rethrowDeferredStartupExceptions();
    285. *
    286. * try {
    287. * ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
    288. * }
    289. * catch (NamingException ex) {
    290. * // Naming is not enabled. Continue
    291. * }
    292. *
    293. * // Unlike Jetty, all Tomcat threads are daemon threads. We create a
    294. * // blocking non-daemon to stop immediate shutdown
    295. * startDaemonAwaitThread();
    296. * }
    297. * catch (Exception ex) {
    298. * stopSilently();
    299. * destroySilently();
    300. * throw new WebServerException("Unable to start embedded Tomcat", ex);
    301. * }
    302. * }
    303. * }
    304. */
    305. System.out.println("hello ioc");
    306. }
    307. }

    重点

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

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

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

    1.1说明: 创建 Tomcat, 并启动

    1.2分析+代码实现

    ● 代码实现        

    1.修改pom.xml

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.bootgroupId>
    4. <artifactId>spring-boot-starter-webartifactId>
    5. <exclusions>
    6. <exclusion>
    7. <groupId>org.springframework.bootgroupId>
    8. <artifactId>spring-boot-starter-tomcatartifactId>
    9. exclusion>
    10. exclusions>
    11. dependency>
    12. <dependency>
    13. <groupId>org.apache.tomcat.embedgroupId>
    14. <artifactId>tomcat-embed-coreartifactId>
    15. <version>8.5.75version>
    16. dependency>
    17. dependencies>

    2.SpringApplication.java

    1. public class HongSpringApplication {
    2. //这里我们会创建tomcat对象,并关联Spring容器, 并启动
    3. public static void run() {
    4. try {
    5. //创建Tomcat对象 HspTomcat
    6. Tomcat tomcat = new Tomcat();
    7. //设置9090
    8. tomcat.setPort(9090);
    9. //启动
    10. tomcat.start();
    11. //等待请求接入
    12. System.out.println("======9090====等待请求=====");
    13. tomcat.getServer().await();
    14. } catch (Exception e) {
    15. e.printStackTrace();
    16. }
    17. }
    18. }

    3.MainApp.java

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

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

    2.1说明: 创建 Spring 容器

    2.2 分析+代码实现

    代码实现

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

    1. public class Monster {
    2. }

    2.HiController.java, 作为 Controller

    1. @RestController
    2. public class HiController {
    3. @RequestMapping("/hi")
    4. public String hi() {
    5. return "hi,hong HiController";
    6. }
    7. }

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

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

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

    1. /**
    2. * @author 海绵hong
    3. * @version 1.0
    4. * Initializer: 初始化器
    5. */
    6. /**
    7. * 解读
    8. * 1. 创建我们的Spring 容器
    9. * 2. 加载/关联Spring容器的配置-按照注解的方式
    10. * 3. 完成Spring容器配置的bean的创建, 依赖注入
    11. * 4. 创建前端控制器 DispatcherServlet , 并让其持有Spring容器
    12. * 5. 当DispatcherServlet 持有容器, 就可以进行分发映射, 请小伙伴回忆我们实现SpringMVC底层机制
    13. * 6. 这里onStartup 是Tomcat调用, 并把ServletContext 对象传入
    14. */
    15. public class HspWebApplicationInitializer implements WebApplicationInitializer {
    16. @Override
    17. public void onStartup(ServletContext servletContext) throws ServletException {
    18. System.out.println("startup ....");
    19. //加载Spring web application configuration => 容器
    20. //自己 写过 HongSpringApplicationContext
    21. AnnotationConfigWebApplicationContext ac =
    22. new AnnotationConfigWebApplicationContext();
    23. //在ac中注册 HongConfig.class 配置类
    24. ac.register(HongConfig.class);
    25. ac.refresh();//完成bean的创建和配置
    26. //1. 创建注册非常重要的前端控制器 DispatcherServlet
    27. //2. 让DispatcherServlet 持有容器
    28. //3. 这样就可以进行映射分发, 回忆一下SpringMvc机制[自己实现过]
    29. //HongDispatcherServlet
    30. DispatcherServlet dispatcherServlet = new DispatcherServlet(ac);
    31. //返回了ServletRegistration.Dynamic对象
    32. ServletRegistration.Dynamic registration =
    33. servletContext.addServlet("app", dispatcherServlet);
    34. //当tomcat启动时,加载 dispatcherServlet
    35. registration.setLoadOnStartup(1);
    36. //拦截请求,并进行分发处理
    37. //这里在提示 / 和 /* => 在讲解 java web , 自己去看看.
    38. registration.addMapping("/");
    39. }
    40. }

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

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

    3.2 分析+代码实现

    代码实现
    1.SpringApplication.java
    1. public class HongSpringApplication {
    2. //这里我们会创建tomcat对象,并关联Spring容器, 并启动
    3. public static void run() {
    4. try {
    5. //创建Tomcat对象 HspTomcat
    6. Tomcat tomcat = new Tomcat();
    7. //1. 让tomcat可以将请求转发到spring web容器,因此需要进行关联
    8. //2. "/hong" 就是我们的项目的 application context , 就是我们原来配置tomcat时,指定的application context
    9. //3. "D:\\hspedu_springboot\\hsp-springboot" 指定项目的目录
    10. tomcat.addWebapp("/hong","D:\\hong_springboot\\hong-springboot");
    11. //设置9090
    12. tomcat.setPort(9090);
    13. //启动
    14. tomcat.start();
    15. //等待请求接入
    16. System.out.println("======9090====等待请求=====");
    17. tomcat.getServer().await();
    18. } catch (Exception e) {
    19. e.printStackTrace();
    20. }
    21. }
    22. }

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

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

    结果:

  • 相关阅读:
    Vue高级篇--实现前后端完全分离
    C++ 多态
    Java电子招投标采购系统源码-适合于招标代理、政府采购、企业采购、等业务的企业
    Pr:场景编辑检测
    SpringBoot和Vue实现用户登录注册与异常处理——基于SpringBoot和Vue的后台管理系统项目系列博客(十三)
    多线程拷贝文件
    模式分解的概念(下)-无损连接分解的与保持函数依赖分解的定义和判断、损失分解
    搞懂TypeScript的类型声明
    VB.net webbrowser 自定义下载接口实现
    初识OpenGL (-)VAO顶点数组对象
  • 原文地址:https://blog.csdn.net/weixin_54107527/article/details/127907297