SpringBoot 底层机制分析

  |   0 评论   |   0 浏览

SpringBoot 底层机制分析

主要内容:【Tomcat 启动分析 + Spring 容器初始化 +Tomcat 如何关联 Spring 容器 】

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

1、创建 Maven 项目 llp-springboot

image-20220730103418193

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 也启动了[如何实现的?]

image-20220730103605413

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实例

image-20220730104019367

这里有一个小的技巧,我们这里查看singletonObjects单例池时时简化的显示的,如果需要查看完成数据结构可以参考如下设置

image-20220730104124449

image-20220730104348459

再次查看,可以看到debug集合显示的数据结构更完整了,这个设置可以按需灵活切换

image-20220730104431775

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.访问测试

image-20220730104734278

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.完成测试

image-20220730181035195

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();
        }
    }

}
  1. debug 一下, 看看是否进行 Spring 容器的初始化工作, 可以看到 ac.refresh() 会将 LLPBeanConfig.class 中配置 Bean 实例化装入到容器中

可以看到,在 ac.refresh()之前,beanFactory为空

image-20220730181735321

在执行了 ac.refresh()之后,将bean被注入到了容器中

image-20220730181908330

3.完成测试

image-20220730182004664

image-20220730182124728

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>

标题:SpringBoot 底层机制分析
作者:llp
地址:https://llinp.cn/articles/2022/08/09/1660051475885.html