小言_互联网的博客

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

383人阅读  评论(0)

目录

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

重点

就是创建了一个容器,注入了相应的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.boot </groupId>
  4. <artifactId>spring-boot-starter-web </artifactId>
  5. <!--因为我们自己要创建Tomcat对象,并启动,
  6. 因此我们先排除 内嵌的 spring-boot-starter-tomcat-->
  7. <exclusions>
  8. <exclusion>
  9. <groupId>org.springframework.boot </groupId>
  10. <artifactId>spring-boot-starter-tomcat </artifactId>
  11. </exclusion>
  12. </exclusions>
  13. </dependency>
  14. <!--我们指定tomcat版本,引入tomcat依赖/库
  15. 1. 使用指定的tomcat 8.5.75
  16. 2. 如果我们引入自己指定的tomcat,一定要记住把前面spring-boot-starter-tomcat排除
  17. 3. 如果你不排除,会出现 GenericServlet Not Found错误提示
  18. -->
  19. <dependency>
  20. <groupId>org.apache.tomcat.embed </groupId>
  21. <artifactId>tomcat-embed-core </artifactId>
  22. <version>8.5.75 </version>
  23. </dependency>
  24. </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 实例化装入到容器中 .

 

 

结果:


转载:https://blog.csdn.net/weixin_54107527/article/details/127907297
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场