SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
Spring Boot默认使用Tomcat作为嵌入式的Servlet容器,只要引入了spring-boot-start-web依赖,则默认是用Tomcat作为Servlet容器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Servlet容器的使用
默认servlet容器
我们看看spring-boot-starter-web这个starter中有什么

核心就是引入了tomcat和SpringMvc,我们先来看tomcat
Spring Boot默认支持Tomcat,Jetty,和Undertow作为底层容器。如图:

而Spring Boot默认使用Tomcat,一旦引入spring-boot-starter-web模块,就默认使用Tomcat容器。
切换servlet容器
那如果我么想切换其他Servlet容器呢,只需如下两步:
- 将tomcat依赖移除掉
- 引入其他Servlet容器依赖
引入jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<!--移除spring-boot-starter-web中的tomcat-->
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<!--引入jetty-->
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Servlet容器自动配置原理
EmbeddedServletContainerAutoConfiguration

我们可以看到EmbeddedServletContainerAutoConfiguration被配置在spring.factories中,看过我前面文章的朋友应该知道SpringBoot自动配置的原理,这里将EmbeddedServletContainerAutoConfiguration配置类加入到IOC容器中,接着我们来具体看看这个配置类:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication// 在Web环境下才会起作用
@Import(BeanPostProcessorsRegistrar.class)// 会Import一个内部类BeanPostProcessorsRegistrar
public class EmbeddedServletContainerAutoConfiguration { @Configuration
// Tomcat类和Servlet类必须在classloader中存在
// 文章开头我们已经导入了web的starter,其中包含tomcat和SpringMvc
// 那么classPath下会存在Tomcat.class和Servlet.class
@ConditionalOnClass({ Servlet.class, Tomcat.class })
// 当前Spring容器中不存在EmbeddedServletContainerFactory类型的实例
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat { @Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
// 上述条件注解成立的话就会构造TomcatEmbeddedServletContainerFactory这个EmbeddedServletContainerFactory
return new TomcatEmbeddedServletContainerFactory();
}
} @Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty { @Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
} } @Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow { @Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
} } //other code...
}
在这个自动配置类中配置了三个容器工厂的Bean,分别是:
- TomcatEmbeddedServletContainerFactory 
- JettyEmbeddedServletContainerFactory 
- UndertowEmbeddedServletContainerFactory 
EmbeddedServletContainerFactory
- 嵌入式Servlet容器工厂
public interface EmbeddedServletContainerFactory {
    EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers);
}
内部只有一个方法,用于获取嵌入式的Servlet容器。
该工厂接口主要有三个实现类,分别对应三种嵌入式Servlet容器的工厂类,如图所示:

TomcatEmbeddedServletContainerFactory
以Tomcat容器工厂TomcatEmbeddedServletContainerFactory类为例:
public class TomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
    //other code...
    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) {
        //创建一个Tomcat
        Tomcat tomcat = new Tomcat();
       //配置Tomcat的基本环节
        File baseDir = (this.baseDirectory != null ? this.baseDirectory: createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        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);
        //包装tomcat对象,返回一个嵌入式Tomcat容器,内部会启动该tomcat容器
        return getTomcatEmbeddedServletContainer(tomcat);
    }
}
首先会创建一个Tomcat的对象,并设置一些属性配置,最后调用getTomcatEmbeddedServletContainer(tomcat)方法,内部会启动tomcat,我们来看看:
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}
该函数很简单,就是来创建Tomcat容器并返回。看看TomcatEmbeddedServletContainer类:
public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
    public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        //初始化嵌入式Tomcat容器,并启动Tomcat
        initialize();
    }
    private void initialize() throws EmbeddedServletContainerException {
        TomcatEmbeddedServletContainer.logger
                .info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                addInstanceIdToEngineName();
                try {
                    final Context context = findContext();
                    context.addLifecycleListener(new LifecycleListener() {
                        @Override
                        public void lifecycleEvent(LifecycleEvent 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
                    //启动tomcat
                    this.tomcat.start();
                    // We can re-throw failure exception directly in the main thread
                    rethrowDeferredStartupExceptions();
                    try {
                        ContextBindings.bindClassLoader(context, getNamingToken(context),
                                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) {
                    containerCounter.decrementAndGet();
                    throw ex;
                }
            }
            catch (Exception ex) {
                stopSilently();
                throw new EmbeddedServletContainerException(
                        "Unable to start embedded Tomcat", ex);
            }
        }
    }
}
到这里就启动了嵌入式的Servlet容器,其他容器类似。
Servlet容器启动原理
SpringBoot启动过程
我们回顾一下前面讲解的SpringBoot启动过程,也就是run方法:
public ConfigurableApplicationContext run(String... args) {
    // 计时工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 第一步:获取并启动监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 第二步:根据SpringApplicationRunListeners以及参数来准备环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
        Banner printedBanner = printBanner(environment);
        // 第三步:创建Spring容器
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // 第四步:Spring容器前置处理
        prepareContext(context, environment, listeners, applicationArguments,printedBanner);
        // 第五步:刷新容器
        refreshContext(context);
     // 第六步:Spring容器后置处理
        afterRefresh(context, applicationArguments);
      // 第七步:发出结束执行的事件
        listeners.started(context);
        // 第八步:执行Runners
        this.callRunners(context, applicationArguments);
        stopWatch.stop();
        // 返回容器
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, exceptionReporters, ex);
        throw new IllegalStateException(ex);
    }
}
我们回顾一下第三步:创建Spring容器
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext"; public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext"; protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
//根据应用环境,创建不同的IOC容器
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
}
protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    //调用容器的refresh()方法刷新容器
    ((AbstractApplicationContext) applicationContext).refresh();
}
容器刷新过程
调用抽象父类AbstractApplicationContext的refresh()方法;
AbstractApplicationContext
 public void refresh() throws BeansException, IllegalStateException {
     synchronized (this.startupShutdownMonitor) {
         /**
          * 刷新上下文环境
          */
         prepareRefresh();
         /**
          * 初始化BeanFactory,解析XML,相当于之前的XmlBeanFactory的操作,
          */
         ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
         /**
          * 为上下文准备BeanFactory,即对BeanFactory的各种功能进行填充,如常用的注解@Autowired @Qualifier等
          * 添加ApplicationContextAwareProcessor处理器
          * 在依赖注入忽略实现*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
          * 注册依赖,如一个bean的属性中含有ApplicationEventPublisher(beanFactory),则会将beanFactory的实例注入进去
          */
         prepareBeanFactory(beanFactory);
         try {
             /**
              * 提供子类覆盖的额外处理,即子类处理自定义的BeanFactoryPostProcess
              */
             postProcessBeanFactory(beanFactory);
             /**
              * 激活各种BeanFactory处理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
              * 执行对应的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
              */
             invokeBeanFactoryPostProcessors(beanFactory);
             /**
              * 注册拦截Bean创建的Bean处理器,即注册BeanPostProcessor,不是BeanFactoryPostProcessor,注意两者的区别
              * 注意,这里仅仅是注册,并不会执行对应的方法,将在bean的实例化时执行对应的方法
              */
             registerBeanPostProcessors(beanFactory);
             /**
              * 初始化上下文中的资源文件,如国际化文件的处理等
              */
             initMessageSource();
             /**
              * 初始化上下文事件广播器,并放入applicatioEventMulticaster,如ApplicationEventPublisher
              */
             initApplicationEventMulticaster();
             /**
              * 给子类扩展初始化其他Bean
              */
             onRefresh();
             /**
              * 在所有bean中查找listener bean,然后注册到广播器中
              */
             registerListeners();
             /**
              * 设置转换器
              * 注册一个默认的属性值解析器
              * 冻结所有的bean定义,说明注册的bean定义将不能被修改或进一步的处理
              * 初始化剩余的非惰性的bean,即初始化非延迟加载的bean
              */
             finishBeanFactoryInitialization(beanFactory);
             /**
              * 通过spring的事件发布机制发布ContextRefreshedEvent事件,以保证对应的监听器做进一步的处理
              * 即对那种在spring启动后需要处理的一些类,这些类实现了ApplicationListener<ContextRefreshedEvent>,
              * 这里就是要触发这些类的执行(执行onApplicationEvent方法)
              * spring的内置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
              * 完成初始化,通知生命周期处理器lifeCycleProcessor刷新过程,同时发出ContextRefreshEvent通知其他人
              */
             finishRefresh();
         }
         finally {
             resetCommonCaches();
         }
     }
 }
我们看第52行的方法:
protected void onRefresh() throws BeansException {
}
很明显抽象父类AbstractApplicationContext中的onRefresh是一个空方法,并且使用protected修饰,也就是其子类可以重写onRefresh方法,那我们看看其子类AnnotationConfigEmbeddedWebApplicationContext中的onRefresh方法是如何重写的,AnnotationConfigEmbeddedWebApplicationContext又继承EmbeddedWebApplicationContext,如下:
public class AnnotationConfigEmbeddedWebApplicationContext extends EmbeddedWebApplicationContext {
那我们看看其父类EmbeddedWebApplicationContext 是如何重写onRefresh方法的:
EmbeddedWebApplicationContext
@Override
protected void onRefresh() {
super.onRefresh();
try {
//核心方法:会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container", ex);
}
}
在createEmbeddedServletContainer方法中会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器:
 private void createEmbeddedServletContainer() {
     EmbeddedServletContainer localContainer = this.embeddedServletContainer;
     ServletContext localServletContext = getServletContext();
     if (localContainer == null && localServletContext == null) {
         //先获取嵌入式Servlet容器工厂
         EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
         //根据容器工厂来获取对应的嵌入式Servlet容器
         this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
     }
     else if (localServletContext != null) {
         try {
             getSelfInitializer().onStartup(localServletContext);
         }
         catch (ServletException ex) {
             throw new ApplicationContextException("Cannot initialize servlet context",ex);
         }
     }
     initPropertySources();
 }
关键代码在第6和第8行,先获取Servlet容器工厂,然后根据容器工厂来获取对应的嵌入式Servlet容器
获取Servlet容器工厂
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
    //从Spring的IOC容器中获取EmbeddedServletContainerFactory.class类型的Bean
    String[] beanNames = getBeanFactory().getBeanNamesForType(EmbeddedServletContainerFactory.class);
    //调用getBean实例化EmbeddedServletContainerFactory.class
    return getBeanFactory().getBean(beanNames[0], EmbeddedServletContainerFactory.class);
}
我们看到先从Spring的IOC容器中获取EmbeddedServletContainerFactory.class类型的Bean,然后调用getBean实例化EmbeddedServletContainerFactory.class,大家还记得我们第一节Servlet容器自动配置类EmbeddedServletContainerAutoConfiguration中注入Spring容器的对象是什么吗?当我们引入spring-boot-starter-web这个启动器后,会注入TomcatEmbeddedServletContainerFactory这个对象到Spring容器中,所以这里获取到的Servlet容器工厂是TomcatEmbeddedServletContainerFactory,然后调用
TomcatEmbeddedServletContainerFactory的getEmbeddedServletContainer方法获取Servlet容器,并且启动Tomcat,大家可以看看文章开头的getEmbeddedServletContainer方法。
大家看一下第8行代码获取Servlet容器方法的参数getSelfInitializer(),这是个啥?我们点进去看看
private ServletContextInitializer getSelfInitializer() {
    //创建一个ServletContextInitializer对象,并重写onStartup方法,很明显是一个回调方法
    return new ServletContextInitializer() {
        public void onStartup(ServletContext servletContext) throws ServletException {
            EmbeddedWebApplicationContext.this.selfInitialize(servletContext);
        }
    };
}
创建一个ServletContextInitializer对象,并重写onStartup方法,很明显是一个回调方法,这里给大家留一点疑问:
- ServletContextInitializer对象创建过程是怎样的?
- onStartup是何时调用的?
- onStartup方法的作用是什么?
ServletContextInitializer是 Servlet 容器初始化的时候,提供的初始化接口。这里涉及到Servlet、Filter实例的注册,我们留在下一篇具体讲
SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)的更多相关文章
- Spring Boot → 09:使用外置Servlet容器_tomcat9.0
		Spring Boot → 09:使用外置Servlet容器_tomcat9.0 
- 170322、Spring Boot 性能优化之将Servlet容器变成Undertow
		需求缘起:在研究Spring Boot加速启动的时候,发现我们在实际中,可能比较注重我们服务器的内存的情况,那么我们会想如果在启动的时候,所占用的内存越低是越好,基于这个想法,我们看看Spring B ... 
- SpringBoot零XML配置的Spring Boot Application
		Spring Boot 提供了一种统一的方式来管理应用的配置,允许开发人员使用属性properties文件.YAML 文件.环境变量和命令行参数来定义优先级不同的配置值.零XML配置的Spring B ... 
- Spring Boot 启动源码解析结合Spring Bean生命周期分析
		转载请注明出处: 1.SpringBoot 源码执行流程图 2. 创建SpringApplication 应用,在构造函数中推断启动应用类型,并进行spring boot自动装配 public sta ... 
- Spring Boot启动命令参数详解及源码分析
		使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ... 
- Celery 源码解析六:Events 的实现
		在 Celery 中,除了远程控制之外,还有一个元素可以让我们对分布式中的任务的状态有所掌控,而且从实际意义上来说,这个元素对 Celery 更为重要,这就是在本文中将要说到的 Event. 在 Ce ... 
- Spring5源码解析-论Spring DispatcherServlet的生命周期
		Spring Web框架架构的主要部分是DispatcherServlet.也就是本文中重点介绍的对象. 在本文的第一部分中,我们将看到基于Spring的DispatcherServlet的主要概念: ... 
- springboot(十九):使用Spring Boot Actuator监控应用
		微服务的特点决定了功能模块的部署是分布式的,大部分功能模块都是运行在不同的机器上,彼此通过服务调用进行交互,前后台的业务流会经过很多个微服务的处理和传递,出现了异常如何快速定位是哪个环节出现了问题? ... 
- Spring Boot 2.0 教程 | 配置 Undertow 容器
		欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 文章首发于个人网站 https://ww ... 
随机推荐
- call,apply和bind详解
			一.call和apply call和apply其实是同一个东西,区别只有参数不同,call是apply的语法糖,所以就放在一起说了,这两个方法都是定义在函数对象的原型上的(Function.proto ... 
- Cocos2d-x 学习笔记(25) 渲染 绘制 Render
			[Cocos2d-x]学习笔记目录 本文链接:https://www.cnblogs.com/deepcho/p/cocos2dx-render.html 1. 从程序入口到渲染方法 一个Cocos2 ... 
- CVE-2016-7124漏洞复现
			CVE-2016-7124漏洞复现 __wakeup()魔术方法绕过 实验环境 操作机:Windows 10 服务器:apache 2.4 数据库:mysql 5.0 PHP版本:5.5 漏洞影响版本 ... 
- mfc字符转码
			std::wstring UTF8ToUnicode(const std::string& utf8string) { , utf8string.c_str(), -, NULL, ); ... 
- std::lock_guard 与 std::unique_lock
			std::lock_guard 与 std::unique_lock 对 mutex 进行自动加解锁. mutex m; void fun() { unique_lock<mutex> m ... 
- Java中线程与堆栈的关系
			栈是线程私有的,每个线程都是自己的栈,每个线程中的每个方法在执行的同时会创建一个栈帧用于存局部变量表.操作数栈.动态链接.方法返回地址等信息.每一个方法从调用到执行完毕的过程,就对应着一个栈帧在虚拟机 ... 
- 小白学微信小程序
			奔着实用性的目的-测试孩子的认字量,开发了一个微信小程序-测字大王.上下班路上看书看了一个星期,代码前后共写一个星期.现在小程序已经对外开放,share下我的开发过程吧. 一 工具准备 首先先过一篇 ... 
- 【原】iOS开发进阶(唐巧)读书笔记(二)
			第三部分:iOS开发底层原理 1.Objective-C对象模型 1.1 isa指针 NSObject.h部分代码: NS_ROOT_CLASS @interface NSObject <NSO ... 
- nginx原理和优化
			Nginx的模块与工作原理 Nginx由内核和模块组成,其中,内核的设计非常微小和简洁,完成的工作也非常简单,仅仅通过查找配置文件将客户端请求映射到一个location block(location是 ... 
- 图片瀑布流,so easy!
			什么是图片瀑布流 用一张花瓣网页的图片布局可以很清楚看出图片瀑布流的样子: 简单来说,就是有很多图片平铺在页面上,每张图片的宽度相同,但是高度不同,这样错落有致的排列出 n 列的样子很像瀑布,于是就有 ... 
