四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat

四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat

@


1. 源码分析 Spring Boot是如何启动 Tomcat ,并支持访问 @Controller 的 Debug 流程分析

进行源码分析,自然是少不了,Debug 的。下面就让我们打上断点 ,Debug起来吧

1.1 源码分析: SpringApplication.run( ) 方法

SpringApplication.run()
DeBug  SpringApplication.run(MainApp.class, args); 看看 Spring Boot 是如何启动 Tomcat的 

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

  1. SpringApplication.java

在这里插入图片描述

在这里插入图片描述

// 这里我们开始 Debug SpringApplication。run()
   public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }

  1. SpringApplication.java

在这里插入图片描述


    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 = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();  // 特别分析: 创建容器
            context.setApplicationStartup(this.applicationStartup);
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);  // 特别分析:刷新应用程序上下文,比如:初始化默认设置/注入相关Bean/启动 tomcat
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
  1. SpringApplication.java : 容器类型很多,会根据你的 this.webApplicationType 创建对应的容器
    默认 this.webApplicationType 是 servlet 也就是 web 容器/处理的 servlet

在这里插入图片描述


    protected ConfigurableApplicationContext createApplicationContext() {
        return this.applicationContextFactory.create(this.webApplicationType);
    }

  1. ApplicationContextFactory.java
    默认是进入这个分支 case SERVLET: 返回 new AnnotationConfigServletWebServerApplicationContext();

在这里插入图片描述

public interface ApplicationContextFactory {
    ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {
            switch(webApplicationType) {
            case SERVLET:  // 默认是进入这个分支
                return new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE:
                return new AnnotationConfigReactiveWebServerApplicationContext();
            default:
                return new AnnotationConfigApplicationContext();
            }
        } catch (Exception var2) {
            throw new IllegalStateException("Unable create a default ApplicationContext instance, you may need a custom ApplicationContextFactory", var2);
        }
    };

    ConfigurableApplicationContext create(WebApplicationType webApplicationType);

    static ApplicationContextFactory ofContextClass(Class<? extends ConfigurableApplicationContext> contextClass) {
        return of(() -> {
            return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
        });
    }

    static ApplicationContextFactory of(Supplier<ConfigurableApplicationContext> supplier) {
        return (webApplicationType) -> {
            return (ConfigurableApplicationContext)supplier.get();
        };
    }
}
  1. SpringApplication.java

在这里插入图片描述

在这里插入图片描述

    private void refreshContext(ConfigurableApplicationContext context) {
        if (this.registerShutdownHook) {
            shutdownHook.registerApplicationContext(context);
        }
        this.refresh(context);  // 特别分析,真正执行相关任务
    }

  1. SpringApplication.java

在这里插入图片描述


    protected void refresh(ConfigurableApplicationContext applicationContext) {
        applicationContext.refresh();
    }
  1. Servlet 中的 ServletWebServerApplicationContext.java

在这里插入图片描述

    public final void refresh() throws BeansException, IllegalStateException {
        try {
            super.refresh();  // 特别分析这个方法
        } catch (RuntimeException var3) {
            WebServer webServer = this.webServer;
            if (webServer != null) {
                webServer.stop();
            }

            throw var3;
        }
    }
  1. ApplicationContextFactory .java

在这里插入图片描述


 public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();  // 特别分析,当父类完成通用的工作后,再重新动态绑定机制回到
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var10) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
                }

                this.destroyBeans();
                this.cancelRefresh(var10);
                throw var10;
            } finally {
                this.resetCommonCaches();
                contextRefresh.end();
            }

        }
    }
  1. ServletWebServerApplicationContext.java
    在这里插入图片描述

 protected void onRefresh() {
        super.onRefresh();

        try {
            this.createWebServer();  创建 webServer 可以理解成会创建指定 web服务器-tomcat
        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start web server", var2);
        }
    }
  1. ServletWebServerApplicationContext.java

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

 private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = this.getServletContext();
        if (webServer == null && servletContext == null) {
            StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
            ServletWebServerFactory factory = this.getWebServerFactory();
            createWebServer.tag("factory", factory.getClass().toString());
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer
()}); // 特别分析,使用 TomcatServletWebServerFactory 创建一个TomcatWEbServer
            createWebServer.end();
            this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
            this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
        } else if (servletContext != null) {
            try {
                this.getSelfInitializer().onStartup(servletContext);
            } catch (ServletException var5) {
                throw new ApplicationContextException("Cannot initialize servlet context", var5);
            }
        }

        this.initPropertySources();
    }

12.TomcatServletWebServerFactory.java 会创建Tomcat 并启动 Tomcat

在这里插入图片描述

 public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }

        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);
        return this.getTomcatWebServer(tomcat);
    }
  1. TomcatServletWebServerFactory.java

在这里插入图片描述


    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
    }

  1. TomcatWebServer.java

在这里插入图片描述

    public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
        this.monitor = new Object();
        this.serviceConnectors = new HashMap();
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null;
        this.initialize(); // 分析这个方法
    }
  1. TomcatWebServer.java this.tomcat.start(); 初始化 Tomcat 服务器。

在这里插入图片描述

 private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
        synchronized(this.monitor) {
            try {
                this.addInstanceIdToEngineName();
                Context context = this.findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                        this.removeServiceConnectors();
                    }

                });
                this.tomcat.start();   // *********     启动 Tomcat 
                this.rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
                } catch (NamingException var5) {
                }

                this.startDaemonAwaitThread();
            } catch (Exception var6) {
                this.stopSilently();
                this.destroySilently();
                throw new WebServerException("Unable to start embedded Tomcat", var6);
            }

        }
    }

2. 自己编写实现 Spring Boot 底层机制【Tomcat启动分析 + Spring容器初始化 + Tomcat如何关联 Spring容器】

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

依靠 Maven 在 pom.xml 文件中配置相应所需的 jar 包。

在这里插入图片描述

<?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.rainbowsea</groupId>
    <artifactId>mySpringBoot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--    导入SpringBoot 父工程-规定写法-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
    </parent>


    <!--    导入web项目场景启动器:会自动导入和web开发相关的jar包所有依赖【库/jar】-->
    <!--    后面还会在说明spring-boot-starter-web 到底引入哪些相关依赖-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
<!--
        因为我们自己要创建 Tomcat 对象,并启动,
        因此我们先排除 内嵌的 spring-boot-starter-tomcat
-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

<!--        我们指定tomcat版本,引入 tomcat 依赖/库-->
<!--
            1. 使用指定的tomcat 8.5.75 ,请小伙伴也引入这个版本
            2. 如果我们引入自己指定的tomcat,一定要记住把前面spring-boot-stater-tomcat 排除
            3. 如果你不排除,会出现 GenericServlet Not Found错误
-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.5.75</version>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jasper</artifactId>
            <version>8.5.75</version>
        </dependency>
    </dependencies>

</project>

2.1 实现任务阶段1:创建Tomcat 并启动

在这里插入图片描述

package com.rainbowsea.myspringboot;


import org.apache.catalina.startup.Tomcat;

public class MySpringApplication {

    // 这里我们会创建一个tomcat对象,并关联Spring容器,并启动
    public static void run() {

        try {
            // 创建tomcat对象
            Tomcat tomcat = new Tomcat();

            // 1. 让tomcat 可以将请求转发到spring web 容器,因此需要进行关联
            // 2. “/myspboot 就是我们的项目的 application context ,就是我们原来配置tomcat时,指定application
            tomcat.addWebapp("/app","E:\\Java\\SpringBoot\\quickstart\\mySpringBoot");

            //
            // 设置监视端口为 9090
            tomcat.setPort(8080);

            // 启动 Tomcat
            tomcat.start();

            // 等待请求接入
            System.out.println("===9090===等待请求=========");
            tomcat.getServer().await();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

        }
    }
}

2.2 实现任务阶段2:创建Spring容器

bean 对象。

在这里插入图片描述

package com.rainbowsea.myspringboot.bean;


import org.springframework.stereotype.Controller;

@Controller  // 加入到 ioc 容器当中管理
public class Monster {

}

bean 对象对应的 config 配置类

在这里插入图片描述

package com.rainbowsea.myspringboot.config;


import com.rainbowsea.myspringboot.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


/*
这里有一个问题,容器怎么知道要扫描哪些包?
在配置类可以指定要扫描包: @ComponentScan("com.rainbowsea.myspringboot")
MyConfig 配置类-作为Spring的配置文件
 */
@ComponentScan("com.rainbowsea.myspringboot.bean")
@Configuration  // 标注: 设置类
public class MyConfig {

    // 注入Bean - monster 对象到 Spring 容器
    @Bean
    public Monster monster() {
        return new Monster();
    }

}

controller 处理业务请求的控制器

在这里插入图片描述

package com.rainbowsea.myspringboot.controller;


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController   // @Controller +  @ResponseBod
public class MyHiController {

    @RequestMapping("/myhi")  // 设置请求映射路径
    public String hi() {
        return "hi my MyHiController ";
    }

}

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

在这里插入图片描述

package com.rainbowsea.myspringboot;

import com.rainbowsea.myspringboot.config.MyConfig;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;


/**
 * 解读:
 * 1. 创建我们的Spring容器
 * 2. 加载/关联Spring容器的配置-按照注解的方式
 * 3. 完成Spring容器配置的bean的创建,依赖注入
 * 4. 创建前端控制器 DispatcherServlet,并让其持有Spring容器
 * 5. 当DispatcherServlet 持有容器,就可以进行分发映射,请小伙伴回忆我们实现SpringMVC
 * 6. 这里onStartup 是Tomcat 调用,并把ServletContext 对象传入
 */
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("startup...");
        // 加载Spring web application configuration => 容器


        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();

        ac.register(MyConfig.class);
        ac.refresh();  // 完成bean的创建和配置

        // 1. 创建注册非常重要的前端控制器 当DispatcherServlet
        // 2. 让 DispatcherServlet 持有容器
        // 3. 这样就可以进行映射分发,回忆一下 SpringMVC 机制(自己实现过)
        DispatcherServlet dispatcherServlet = new DispatcherServlet(ac);

        // 返回  ServletRegistration.Dynamic 对象
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", dispatcherServlet);

        // tomcat 启动时,加载  dispatcherServlet
        registration.setLoadOnStartup(1);

        // 拦截请求,并进行分发处理
        // 这里老师在提示  "/" 和 “/*”  在老师讲解 java web
        registration.addMapping("/");

    }
}

在这里插入图片描述

package com.rainbowsea.myspringboot;

public class MyMainApp {


    public static void main(String[] args) {
        // 启动MySpringBoot 项目/程序
        MySpringApplication.run();
    }
}

3. 最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”

在这里插入图片描述

posted @ 2024-08-30 20:49  Rainbow-Sea  阅读(175)  评论(0编辑  收藏  举报