SpringBoot 底层机制分析
SpringBoot 底层机制分析
主要内容:【Tomcat 启动分析 + Spring 容器初始化 +Tomcat 如何关联 Spring 容器 】
1.搭建 SpringBoot 底层机制开发环境
1、创建 Maven 项目 llp-springboot
2、修改pom.xml,导入相关的依赖
<!--
导入springboot父工程-规定写法
父工程只是指定了依赖的版本,需要使用的依赖(jar包)还需要手动的引入
-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3.创建src\main\java\com\llp\springboot\MainApp.java
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);
System.out.println(ioc);
}
}
4、启动项目 ok, Tomcat 也启动了[如何实现的?]
2.@Configuration + @Bean 会发生什么,并分析机制
1.创建src\main\java\com\llp\springboot\bean\Dog.java
public class Dog {
}
2.创建src\main\java\com\llp\springboot\config\BeanConfig.java
@Configuration
public class BeanConfig {
@Bean
public Dog dog(){
return new Dog();
}
}
3.启动容器,看看容器中是否已经注入了 dog 实例
可以看到容器已经注入了dog实例
这里有一个小的技巧,我们这里查看singletonObjects单例池时时简化的显示的,如果需要查看完成数据结构可以参考如下设置
再次查看,可以看到debug集合显示的数据结构更完整了,这个设置可以按需灵活切换
3.提出问题:SpringBoot 是怎么启动 Tomcat ,并可以支持访问@Controller
1.创建src\main\java\com\llp\springboot\controller\HelloController.java
@RestController
public class HelloController {
@RequestMapping("/hi")
public String hello(){
return "hello llp-SpringBoot";
}
}
2.访问测试
4.源码分析: SpringApplication.run()
1、Debug SpringApplication.run(MainApp.class, args) 看看 SpringBoot 是如何启动 Tomcat 的.
2、Debug 目标: 紧抓一条线, 就是看到 tomcat 被启动的代码. 比如 tomcat.start()
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
//启动springboot应用程序/项目
//提出问题: 当我们执行run方法时,怎么就启动我们的内置的tomcat?
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 : 运行 Spring 应用程序,创建并刷新一个新的ApplicationContext 。
*
* 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(ioc);
}
}
5.实现 SpringBoot 底层机制 【Tomcat 启动分析 + Spring 容器初始化+Tomcat 如何关联 Spring 容器 】
1.实现任务阶段 1- 创建 Tomcat, 并启动
1.说明: 创建 Tomcat, 并启动
2.分析+代码实现
1.修改pom.xml
<?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.llp</groupId>
<artifactId>llp-springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<!--
导入springboot父工程-规定写法
父工程只是指定了依赖的版本,需要使用的依赖(jar包)还需要手动的引入
-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--
exclusions(排除):用于要排除的依赖
因为我们自己要创建Tomcat对象,并启动,
因此我们先排除 内嵌的 spring-boot-starter-tomcat-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--
我们指定tomcat版本,引入tomcat依赖/库
1. 使用指定的tomcat 8.5.75
2. 如果我们引入自己指定的tomcat,一定要记住把前面spring-boot-starter-tomcat排除
3. 如果你不排除,会出现 GenericServlet Not Found错误提示
-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.75</version>
</dependency>
<!--
解决java.lang.ClassNotFoundException: org.apache.jasper.servlet.JspServlet
-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>8.5.75</version>
</dependency>
</dependencies>
</project>
2.创建src\main\java\com\llp\llpspringboot\LLPSpringApplication.java
public class LLPSpringApplication {
public static void run() {
try {
//创建tomcat对象
Tomcat tomcat = new Tomcat();
//设置端口
tomcat.setPort(9090);
//启动
tomcat.start();
//等待请求接入
System.out.println("=====9090====等待请求");
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
3.创建主启动类src\main\java\com\llp\llpspringboot\LLPMainApp.java
/**
* 模拟主程序
*/
public class LLPMainApp {
public static void main(String[] args) {
LLPSpringApplication.run();
}
}
3.完成测试
2.实现任务阶段 2- 创建 Spring 容器
1.说明: 创建 Spring 容器
2.分析+代码实现
● 代码实现
1.创建bean src\main\java\com\llp\llpspringboot\bean\Monster.java
public class Monster {
}
2.创建配置类
/**
* LLPBeanConfig:配置类-作为Spring的配置文件
* 在配置类可以指定要扫描包: @ComponentScan("com.llp.llpspringboot")
*/
@Component
@ComponentScan("com.llp.llpspringboot")
public class LLPBeanConfig {
@Bean
public Monster monster() {
return new Monster();
}
}
3.创建src\main\java\com\llp\llpspringboot\Controller\LLPHelloController.java
@RestController
public class LLPHelloController {
@RequestMapping("llphi")
public String hello(){
return "hi llp";
}
}
4.创建src\main\java\com\llp\llpspringboot\LLPWebApplicationInitializer.java,作为Spring容器
/**
* 1. 创建我们的Spring 容器
* 2. 加载/关联Spring容器的配置-按照注解的方式
* 3. 完成Spring容器配置的bean的创建, 依赖注入
* 4. 创建前端控制器 DispatcherServlet , 并让其持有Spring容器
* 5. 当DispatcherServlet 持有容器, 就可以进行分发映射
* 6. 这里onStartup 是Tomcat调用, 并把ServletContext 对象传入
*/
public class LLPWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("startup ....");
//加载Spring web application configuration => 容器
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
//在ac中注册 LLPBeanConfig.class 配置类
ac.register(LLPBeanConfig.class);
//完成bean的创建和配置
ac.refresh();
//1. 创建注册非常重要的前端控制器 DispatcherServlet
//2. 让DispatcherServlet 持有容器
//3. 这样就可以进行映射分发
//LLPDispatcherServlet
DispatcherServlet dispatcherServlet = new DispatcherServlet(ac);
//返回了ServletRegistration.Dynamic对象
ServletRegistration.Dynamic registration = servletContext.addServlet("app", dispatcherServlet);
//当tomcat启动时,加载 dispatcherServlet 如果不在启动时加载前端控制器则无法进行请求分发
registration.setLoadOnStartup(1);
//拦截请求,并进行分发处理
// /不会拦截页面,只会拦截路径。
// /* 会路径和页面
// / 和 /* 拦截所有请求进行分发处理
// /* 是拦截所有的文件夹,不包含子文件夹
// /** 是拦截所有的文件夹及里面的子文件夹
registration.addMapping("/");
}
}
3.实现任务阶段 3- 将 Tomcat 和 Spring 容器关联, 并启动 Spring 容器
1.说明: 将 Tomcat 和 Spring 容器关联, 并启动 Spring 容器
2.分析+代码实现
● 代码实现
1.修改src\main\java\com\llp\llpspringboot\LLPSpringApplication.java
public class LLPSpringApplication {
public static void run() {
try {
//创建tomcat对象
Tomcat tomcat = new Tomcat();
//让tomcat可以将请求转发到springweb容器,因此需要进行关联
// ”/llpboot“ 就是项目的工程路径(application context)
// "E:\\IdeaProjects\\llp-springboot" 指定项目的目录
tomcat.addWebapp("/llpboot", "E:\\IdeaProjects\\llp-springboot");
//设置端口
tomcat.setPort(9090);
//启动
tomcat.start();
//等待请求接入
System.out.println("=====9090====等待请求");
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
- debug 一下, 看看是否进行 Spring 容器的初始化工作, 可以看到 ac.refresh() 会将 LLPBeanConfig.class 中配置 Bean 实例化装入到容器中
可以看到,在 ac.refresh()之前,beanFactory为空
在执行了 ac.refresh()之后,将bean被注入到了容器中
3.完成测试
4.注意事项和细节
1、如果启动包异常, 如下:
java.lang.ClassNotFoundException: org.apache.jasper.servlet.JspServlet
2 、解决方案 ,引入对应版本的 jasper 包 即 可 , 修 改 D:\hsp_springboot_temp\hsp-springboot2\pom.xm
<!--
https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jasper
解决java.lang.ClassNotFoundException: org.apache.jasper.servlet.JspServlet
-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>8.5.75</version>
</dependency>