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(...)
方法主要做了两件事:
创建
ApplicationContext
刷新
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(...)
钩子方法,这个方法的调用时机是:
BeanFactory
初始化完毕BeanDefinition
解析完成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了~~
作者:城南花已开丶
链接:https://juejin.cn/post/7019598046199545892