Embedded Servlet Container 是怎样启动的

传统Java Web开发中,开发者需要独立部署Servlet容器,比如Tomcat,并将应用程序打成war包放入容器才能运行,这多多少少有点繁琐且不方便调试,嵌入式Servlet容器的出现改变了这个局面。当使用嵌入式Servlet容器,我们不再需要任何外部设施的支持,应用程序本身就是一个可以独立运行的个体。作为解放生产力的典型代表,SpringBoot默认采用Embedded Tomcat来启动Web应用程序,我们今天就来探究一下吧(基于SpringBoot 2.3.0)。

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

这段代码相信大家都不陌生,它是应用程序的入口,一切的一切都要从这里开始。我们直接跟进SpringApplication.run(...),来到

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

静态的run(...)函数创建了一个SpringApplication的实例,并调用了它的run(...)方法,继续跟进SpringApplication的构造函数

// 无关逻辑已删除
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 根据类路径下是否有对应的 class 文件来判定当前的运行环境
    // 我们引入了 spring-boot-starter-web,因此类型被推断为 SERVLET
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
}

这里我们主要关注SpringBoot是如何推断应用程序的类型的,简单来说是基于类路径下是否存在指定的class文件来判定的,在我们的例子里类型被推断为
WebApplicationType.SERVLET。接着来到run(...)实例方法

// 无关逻辑已删除
public ConfigurableApplicationContext run(String... args) {
    ConfigurableApplicationContext context = null;
    try {
        // 1\. 创建 ApplicationContext
        context = createApplicationContext();
        // 2\. 刷新 ApplicationContext
        refreshContext(context);
    }
    catch (Throwable ex) {
        throw new IllegalStateException(ex);
    }
    return context;
}

省去无关逻辑后,run(...)方法主要做了两件事:

  1. 创建ApplicationContext
  2. 刷新ApplicationContext

嗯?嵌入式Tomcat这就启动起来了?看来奥秘就隐藏在这两步之中。查看这两步的源码,很容易知道ApplicationContext的具体类型是
AnnotationConfigServletWebServerApplicationContext,而刷新无非是调用了它的refresh()方法。

AnnotationConfigServletWebServerApplicationContext

观察
AnnotationConfigServletWebServerApplicationContext的继承树,可以看到,红圈外的是我们非常熟悉的、在传统Web环境下使用的ApplicationContext;红圈内的部分呢,单看名字也能猜到是我们要重点研究的对象——WebServerApplicaitonContext。

再深入一点儿
AnnotationConfigServletWebServerApplicationContext,它继承自ServletWebServerApplicationContext,并且在父类的基础上提供了对Component Scan的支持和对@Configuration配置类的读取、解析。

public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
        implements AnnotationConfigRegistry {
    // 读取并解析 @Configuration 配置类
    private final AnnotatedBeanDefinitionReader reader;

    // 扫描指定包下的所有组件
    private final ClassPathBeanDefinitionScanner scanner;

    // rest of codes are omitted...
}
复制代码

这部分功能是通过代理给
AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner来实现的。这两兄弟也是我们的老朋友了,而且和嵌入式Servlet容器的启动也没啥关系,我们就不多说了。

WebServerApplicaitonContext

接下来我们的重心就落在
WebServerApplicaitonContext上了,先来看一下它的定义

/**
 * 实现此接口的 ApplicationContext 要负责对嵌入式 WebServer 的创建和生命周期管理
 *
 * Interface to be implemented by {@link ApplicationContext application contexts} that
 * create and manage the lifecycle of an embedded {@link WebServer}.
 */
public interface WebServerApplicationContext extends ApplicationContext {
    /**
     * 返回当前 ApplicationContext 创建的 WebServer,并通过返回的 WebServer 引用来管理其生命周期
     */
    WebServer getWebServer();

    /**
     * 命名空间,当应用程序中有多个 WebServer 在运行时可以用来避免歧义
     */
    String getServerNamespace();

    static boolean hasServerNamespace(ApplicationContext context, String serverNamespace) {
        return (context instanceof WebServerApplicationContext) && ObjectUtils
                .nullSafeEquals(((WebServerApplicationContext) context).getServerNamespace(), serverNamespace);
    }
}

// 子接口,可以对 ApplicationContext 进行配置
public interface ConfigurableWebServerApplicationContext extends ConfigurableApplicationContext, WebServerApplicationContext {
    /**
     * 设置命名空间
     */
    void setServerNamespace(String serverNamespace);
}
复制代码

再看看它关联的WebServer的定义

/**
 * 代表一个已经配置好了的 WebServer,比如 Tomcat、Jetty、Netty
 *
 * Simple interface that represents a fully configured web server (for example Tomcat,
 * Jetty, Netty). Allows the server to be {@link #start() started} and {@link #stop()
 * stopped}.
 */
public interface WebServer {
    /**
     * 启动服务器
     */
    void start() throws WebServerException;

    /**
     * 停止服务器
     */
    void stop() throws WebServerException;

    /**
     * 返回服务器监听的端口
     */
    int getPort();

    /**
     * 优雅关闭
     */
    default void shutDownGracefully(GracefulShutdownCallback callback) {
        callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
    }
}
复制代码

WebServer是对嵌入式Servlet容器的抽象,并且它代表的是一个已经完全配置好了的Servlet容器。换句话说,终端用户不需要关注容器的具体细节,只需要知道怎么启动或关闭它;而
WebServerApplicationContext负责创建WebServer,并作为终端用户在合适的时机启动或关闭它(也就是管理其生命周期)。

ServletWebServerApplicationContext

ServletWebServerApplicationContext实现了WebServerApplicationContext接口,而它对WebServer的创建和管理都浓缩在它自身的刷新进程之中,也就是ConfigurableApplicationContext#refresh()被调用的时候。具体地说,ServletWebServerApplicationContext覆写了onRefresh(...)钩子方法,这个方法的调用时机是:

  1. BeanFactory初始化完毕
  2. BeanDefinition解析完成
  3. Non-Lazy Init类型的Bean还未初始化
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        // 创建 WebServer
        createWebServer();
    } catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    // webServer == null:还没有创建 WebServer 实例
    // servletContext == null:没有使用外部容器
    if (webServer == null && servletContext == null) {
        // 1\. 获取一个创建 WebServer 的工厂
        ServletWebServerFactory factory = getWebServerFactory();
        // 2\. 通过工厂创建 WebServer
        this.webServer = factory.getWebServer(getSelfInitializer());
        // 3\. 监听 ApplicationContext 的生命周期
        // 在 ApplicationContext#stop() 时优雅关闭 WebServer
        getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
        // 4\. 监听 ApplicationContext 的生命周期
        // 在 Non-Lazy Init 类型的 Bean 都初始化了之后启动 WebServer
        // 在 ApplicationContext#stop() 时关闭 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);
        }
    }
    // 初始化 ServletContextPropertySource
    // 将 ServletContext 的 init-parameters 暴露到 Environment 中
    initPropertySources();
}
复制代码

创建WebServer的时机是在Non-Lazy Init类型的Bean初始化之前,通过获取BeanFactory中唯一的一个ServletWebServerFactory来执行创建。注意这里携带了一个参数——getSelfInitializer(),这个参数很重要,我们后面再说。

紧接着往BeanFactory中注册了两个SmartLifecycle类型的组件来管理WebServer的生命周期,其中一个用于优雅关闭WebServer,另一个用于启动或停止WebServer。

class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
    private final WebServer webServer;

    private volatile boolean running;

    WebServerGracefulShutdownLifecycle(WebServer webServer) {
        this.webServer = webServer;
    }

    @Override
    public void start() {
        this.running = true;
    }

    @Override
    public void stop() {
        throw new UnsupportedOperationException("Stop must not be invoked directly");
    }

    @Override
    public void stop(Runnable callback) {
        // 优雅关闭 webServer
        this.running = false;
        this.webServer.shutDownGracefully((result) -> callback.run());
    }

    @Override
    public boolean isRunning() {
        return this.running;
    }
}

class WebServerStartStopLifecycle implements SmartLifecycle {

    private final ServletWebServerApplicationContext applicationContext;

    private final WebServer webServer;

    private volatile boolean running;

    WebServerStartStopLifecycle(ServletWebServerApplicationContext applicationContext, WebServer webServer) {
        this.applicationContext = applicationContext;
        this.webServer = webServer;
    }

    @Override
    public void start() {
        // 启动 webServer
        this.webServer.start();
        this.running = true;
        this.applicationContext.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
    }

    @Override
    public void stop() {
        // 关闭 webServer
        this.webServer.stop();
    }

    @Override
    public boolean isRunning() {
        return this.running;
    }

    @Override
    public int getPhase() {
        // 控制对 #stop() 的调用在 WebServerGracefulShutdownLifecycle#stop(Runnable) 之后
        return Integer.MAX_VALUE - 1;
    }
}
复制代码

SmartLifecycle是spring-context定义的基础组件,本篇的主题并不是它。不过为了方便理清调用顺序,这里还是简单说一下:它是由LifecycleProcessor驱动的,在Non-Lazy Init类型的Bean都初始化了之后,ApplicationContext会回调LifecycleProcessor#onRefresh(),并在其中对SmartLifecycle进行处理。

// 源码位于 AbstractApplicationContext
protected void finishRefresh() {
    // Clear context-level resource caches (such as ASM metadata from scanning).
    clearResourceCaches();

    // 初始化 LifecycleProcessor
    initLifecycleProcessor();

    // 回调 LifecycleProcessor#onRefresh()
    // 在 onRefresh() 中逐个调用 SmartLifecycle#start()
    // 当然,这里还有一些过滤条件,我们就不细说了
    getLifecycleProcessor().onRefresh();

    // 发布 ContextRefreshedEvent
    publishEvent(new ContextRefreshedEvent(this));

    // Participate in LiveBeansView MBean, if active.
    LiveBeansView.registerApplicationContext(this);
}
复制代码

至此,嵌入式Servlet容器是如何启动的就分析完了。

ServletContextInitializer

前面提过,ServletWebServerFactory在创建WebServer时会携带一个参数——getSelfInitializer(),它的类型是ServletContextInitializer。

public interface ServletContextInitializer {
   /**
    * 以编程的方式配置 ServletContext,比如注册 Servlet、Filter 等
    */
   void onStartup(ServletContext servletContext) throws ServletException;
}
复制代码

ServletContextInitializer的作用类似于
ServletContainerInitializer,后者是Servlet API提供的标准初始化器。我们同样可以在ServletContextInitializer 中对ServletContext进行配置,区别在于它的生命周期由BeanFactory管理而不是Servlet容器。

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
    // 1\. ServletContext 和 ApplicationContext 相互绑定
    prepareWebApplicationContext(servletContext);
    // 2\. 注册 ServletContextScope
    registerApplicationScope(servletContext);
    // 3\. 往 BeanFactory 中注册 ServletContext 及其相关的 init-parameters
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    // 4\. 重点:通过 ServletRegistrationBean、FilterRegistrationBean 等像 ServletContext 中动态注册 Servlet、Filter 等组件
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}
复制代码

第 1、2 和 第 3 步比较简单,大家自己看看吧,我们重点来看看第 4 步。

// 初始化 ServletContextInitializerBeans,它继承自 AbstractCollection
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());
}

// 构造函数
@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory, Class<? extends ServletContextInitializer>... initializerTypes) {
   this.initializers = new LinkedMultiValueMap<>();
   this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
         : Collections.singletonList(ServletContextInitializer.class);
    // 遍历 BeanFactory 中所有的 ServletContextInitializer
    // 分别处理 ServletRegistrationBean、FilterRegistrationBean 等
    // 最后将它们添加到 initializers 中
   addServletContextInitializerBeans(beanFactory);
    // 遍历 BeanFactory 中的 Servlet、Filter 等,将它们包装成对应的 RegistrationBean
    // 最后将它们添加到 initializers 中
   addAdaptableBeans(beanFactory);
    // 按照 Ordered 接口或 @Order 注解排序
   List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
         .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
         .collect(Collectors.toList());
    // ServletContextInitializerBeans 继承自 AbstractCollection,sortedList 就是其 back list
   this.sortedList = Collections.unmodifiableList(sortedInitializers);
   logMappings(this.initializers);
}
复制代码

ServletContextInitializerBeans在初始化的时候会检索出BeanFactory中所有的RegistrationBean;如果BeanFactory中还存在原生的Servlet、Filter或Servlet Listener类型的Bean,则将它们包装成对应的RegistrationBean,最后对所有的RegistrationBean进行排序。我们就以ServletRegistrationBean来看看它是如何实现向ServletContext中添加Servlet的吧。

从继承树可以看到,ServletRegistrationBean同样实现了ServletContextInitializer,查看其onStartup(...)方法

// 源码位于 RegistrationBean
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
    // 获取描述信息
    String description = getDescription();
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    // 向 servletContext 中注册
    register(description, servletContext);
}
复制代码

DynamicRegistrationBean实现了register(...)方法

@Override
protected final void register(String description, ServletContext servletContext) {
    // 添加 registration
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
        return;
    }
    // 配置 registration
    configure(registration);
}
复制代码

addRegistration(...)最终由ServletRegistrationBean实现

@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
   String name = getServletName();
   return servletContext.addServlet(name, this.servlet);
}
复制代码

不过是直接使用ServletContext.addServlet(...)动态添加了一个Servlet,再无其它。

后记

我们分析了嵌入式Servlet容器是何时创建和启动的,却没有提它是如何创建的。以Tomcat为例,对应的TomcatWebServer封装了Tomcat API,提供了对Connector、ErrorPage等一些列组件的配置,只不过我不太熟这些容器的架构,就不瞎BB了~~

</article>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容