SpringBoot启动流程简析(四)

简介: 在我们之前的web开发中,通常都是将应用打成war包或者将编译之后的应用放到tomcat的webapps目录下(其他的web服务器放到相应的目录下),但是我们在用SpringBoot进行web...

在我们之前的web开发中,通常都是将应用打成war包或者将编译之后的应用放到tomcat的webapps目录下(其他的web服务器放到相应的目录下),但是我们在用SpringBoot进行web开发的时候,只是启动了一个main类,然后就会神奇的发现tomcat竟然也被启动了(SpringBoot也内置了Jetty),SpringBoot是怎么做到的呢?下面我将慢慢揭开它的神秘面纱:
我们之前说过在SpringBoot中web的上下文是AnnotationConfigEmbeddedWebApplicationContext这个类,我们先看简单的一下这个类的UML图:
UML
AnnotationConfigEmbeddedWebApplicationContext这个类继承了EmbeddedWebApplicationContext类,GenericWebApplicationContext类(这个要注意),它还实现了BeanDefinitionRegistry这个接口,还实现了ResourceLoader这个接口,这个类可以说是一个全能类了,这个个类我们先不多说,主要是看它的父类EmbeddedWebApplicationContext这个类。在这个类中重写了refresh方法、onRefresh方法、onClose方法、finishRefresh方法。这里我们先看onRefresh这个方法,

@Override
protected void onRefresh() {
    //调用父类的onRefresh方法(GenericWebApplicationContext)
    super.onRefresh();
    try {
        //创建嵌入式的Servlet容器
        createEmbeddedServletContainer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start embedded container",
                ex);
    }
}

这里我们要重点分析的就是createEmbeddedServletContainer这个方法。

private void createEmbeddedServletContainer() {
    //先获取embeddedServletContainer
    EmbeddedServletContainer localContainer = this.embeddedServletContainer;
    //获取Servlet上下文
    ServletContext localServletContext = getServletContext();
    //EmbeddedWebApplicationContext没有为EmbeddedServletContainer和ServletContext赋初值,也之前也没有调用set方法,所以这里都是为null
    if (localContainer == null && localServletContext == null) {
        //获取EmbeddedServletContainerFactory的实例 1)
        EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
        //获取EmbeddedServletContainer 2)
        this.embeddedServletContainer = containerFactory
                .getEmbeddedServletContainer(getSelfInitializer());
        }
    else if (localServletContext != null) {
        try {
            getSelfInitializer().onStartup(localServletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",
                    ex);
        }
    }
    //初始化属性信息3)
    initPropertySources();
}

我们先看1)处的代码:

    protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
        // Use bean names so that we don't consider the hierarchy
        //从BeanFactory中获取EmbeddedServletContainerFactory类型的Bean,这里没有考虑父BeanFactory
        String[] beanNames = getBeanFactory()
                .getBeanNamesForType(EmbeddedServletContainerFactory.class);
        //如果没有获取到EmbeddedServletContainerFactory类型的Bean,则抛出异常
        if (beanNames.length == 0) {
            throw new ApplicationContextException(
                    "Unable to start EmbeddedWebApplicationContext due to missing "
                            + "EmbeddedServletContainerFactory bean.");
        }
        //如果有一个以上的EmbeddedServletContainerFactory类型的Bean,则抛出异常
        if (beanNames.length > 1) {
            throw new ApplicationContextException(
                    "Unable to start EmbeddedWebApplicationContext due to multiple "
                            + "EmbeddedServletContainerFactory beans : "
                            + StringUtils.arrayToCommaDelimitedString(beanNames));
        }
        //从BeanFactory中获取EmbeddedServletContainerFactory类型的Bean
        return getBeanFactory().getBean(beanNames[0],
                EmbeddedServletContainerFactory.class);
    }

上面的这一段代码,干了这样三件事,查看Spring容器中是否有EmbeddedServletContainerFactory类型的Bean,EmbeddedServletContainerFactory类型的Bean是否多于一个,从Spring容器中获取EmbeddedServletContainerFactory类型的Bean,那么EmbeddedServletContainerFactory的Bean是什么时候被注入到Spring容器中的呢?我们先看一下EmbeddedServletContainerFactory的层次结构:
Web服务器
从上图中我们可以看到,在SpringBoot中为我们内置了三种Web服务器的实现类,TomCat、Jetty、Undertow(没接触过)。我们之前说SpringBoot的四大神器,其中之一是自动配置的功能,关于自动配置的内容我们不做更多的展开,在SpringBoot中有这样一个类EmbeddedServletContainerAutoConfiguration
这个类配置在spring.factories中,它的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,这里你需要知道,在SpringBoot启动的时候会加载这个类,并且这个类上带有Configuration这个注解,所以这个类会被注入到Spring容器中,然后这个类要生效还要有一个条件,即当前环境是web环境!在EmbeddedServletContainerAutoConfiguration中有这样的一段代码:

@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
    @Bean
    public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
        return new TomcatEmbeddedServletContainerFactory();
    }
}

如果当前类路径下有Servlet和Tomcat这两个类,且在当前上下文中没有EmbeddedServletContainerFactory类型的Bean存在,则创建TomcatEmbeddedServletContainerFactory对象并注入到Spring容器中。SpringBoot中内置了TomCat相关的jar(spring-boot-starter-tomcat)。所以这里注入到Spring容器中的EmbeddedServletContainerFactory类型的Bean是TomcatEmbeddedServletContainerFactory。下面我们接着看2)处的代码:

this.embeddedServletContainer = containerFactory
                    .getEmbeddedServletContainer(getSelfInitializer());

这里的containerFactory是TomcatEmbeddedServletContainerFactory的实例,所以这里调用的也是TomcatEmbeddedServletContainerFactory中的getEmbeddedServletContainer方法,

    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers) {
        //创建一个TomCat对象
        Tomcat tomcat = new Tomcat();
        //创建web容器base目录
        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        //创建一个连接器
        Connector connector = new Connector(this.protocol);
        //向tomcat的service中添加连接器
        tomcat.getService().addConnector(connector);
        //定制化Connector
        customizeConnector(connector);
        tomcat.setConnector(connector);
        //自动部署设置为false
        tomcat.getHost().setAutoDeploy(false);
        //配置Engine
        configureEngine(tomcat.getEngine());
        //想tomcat中添加其他的Connector
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        //准备ServletContext的上下文
        prepareContext(tomcat.getHost(), initializers);
        //创建TomcatEmbeddedServletContainer
        return getTomcatEmbeddedServletContainer(tomcat);
    }

上面的代码是不是看到很晕乎?准确的说上面的内容是TomCat的核心结构部分了。我们先从createTempDir这个简单的方法来开始:

protected File createTempDir(String prefix) {
    try {
        //创建临时文件夹
        File tempDir = File.createTempFile(prefix + ".", "." + getPort());
        //如果之前这个文件夹存在的话,则删除这个文件
        tempDir.delete();
        //创建临时文件夹
        tempDir.mkdir();
        //当虚拟机退出的时候删除这个临时文件夹
        tempDir.deleteOnExit();
        return tempDir;
    }
}

大家第一次看到TomCat这个类的时候是不是有点奇怪,怎么会有一个类叫做TomCat呢?既然敢叫TomCat,那肯定不一般,这个类也确实不一般,它整合了TomCat的整个生命周期,串联了TomCat的各个组件。我们接下来再继续说。

相关文章
|
7月前
|
前端开发 Java 应用服务中间件
SpringBoot-Run启动流程
探索Spring Boot启动流程,从加载配置、创建应用上下文、自动配置到启动内嵌服务器。启动类是入口点,`@SpringBootApplication`标记启动,`SpringApplication.run`启动应用。自动配置基于条件注解配置Bean,应用上下文由`SpringApplication`创建并刷新。内嵌服务器如Tomcat随应用启动,简化部署。理解此流程有助于深入掌握Spring Boot。
224 2
|
7月前
|
缓存 Java 程序员
springboot的启动流程总结
springboot的启动流程总结
|
7月前
|
设计模式 Java 容器
SpringBoot2 | SpringBoot启动流程源码分析(二)
SpringBoot2 | SpringBoot启动流程源码分析(二)
85 0
|
7月前
|
设计模式 Java 机器人
SpringBoot3自动配置流程 SPI机制 核心注解 自定义starter
SpringBoot3自动配置流程 SPI机制 核心注解 自定义starter
|
2月前
|
前端开发 Java 数据安全/隐私保护
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
文章通过一个简单的SpringBoot项目,详细介绍了前后端如何实现用户登录功能,包括前端登录页面的创建、后端登录逻辑的处理、使用session验证用户身份以及获取已登录用户信息的方法。
417 2
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
|
6月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的销售项目流程化管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的销售项目流程化管理系统附带文章源码部署视频讲解等
71 3
|
2月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
42 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
2月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
234 2
|
2月前
|
JSON 前端开发 JavaScript
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
本文介绍如何使用 Spring Boot 3.3 实现职责链模式,优化电商订单处理流程。通过将订单处理的各个环节(如库存校验、优惠券核验、支付处理等)封装为独立的处理器,并通过职责链将这些处理器串联起来,实现了代码的解耦和灵活扩展。具体实现包括订单请求类 `OrderRequest`、抽象处理器类 `OrderHandler`、具体处理器实现(如 `OrderValidationHandler`、`VerifyCouponHandler` 等)、以及初始化职责链的配置类 `OrderChainConfig`。
|
4月前
|
XML Java 应用服务中间件
SpringBoot启动流程解析
SpringBoot启动流程解析
64 0