对启动顺序的错误认识

之前一直有个观点,应用运行在Servlet容器中,因为从Servlet容器与Web应用的使用方式来看,确实很有这种感觉。

我们每次都是启动Servlet容器,然后再启动我们的应用程序,比如如果Web应用使用Spring框架的话,先启动Servlet容器,然后才是Spring容器的初始化。

这样就会产生一种错觉,我们写的程序代码,是运行时Servlet容器的,而容器这个词,更是加深了这种误会。

然后遇到了SpringBoot的内嵌Servlet容器,这种情况下,是先初始化我们的Spring容器,在初始化SpringContext的过程中,去启动我们的Servlet容器。

这就尴尬了,颠覆了之前的认知,于是稍微看了下Spring启动Servlet容器的过程,重新理解下Servlet容器。

先Servlet容器后Spring容器

以前我们使用Servlet容器来部署Java的Web应用时,需要在web.xml中做如下配置

<!-- 配置ServletContext 参数 -->
<context-param>
<!-- 参数名,这个是固定的,不能变 -->
<param-name>contextConfigLocation</param-name>
<param-value>
<!-- Spring 配置文件路径 -->
classpath:applicationContext.xml
</param-value>
</context-param>
<!-- 上下文加载器的监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

在web.xml中作如上的配置,在Servlet容器启动成功后,就可以初始化我们的Spring ApplicationContext了

怎么做的呢? 稍微记录下

首先,配置的监听器,会在Servlet容器启动后,由Servlet容器进行一个事件发布,将此事件发布给配置的所有的监听器,以及Servlet容器内部的一些监听器。

org.springframework.web.context.ContextLoaderListener implements ServletContextListener
public class ContextLoaderListener implements ServletContextListener {
private ContextLoader contextLoader; public ContextLoaderListener() {
} public void contextInitialized(ServletContextEvent event) {
this.contextLoader = this.createContextLoader();
// 从ServletContextEvent事件中,获取ServletContext对象
this.contextLoader.initWebApplicationContext(event.getServletContext());
} protected ContextLoader createContextLoader() {
return new ContextLoader();
} public ContextLoader getContextLoader() {
return this.contextLoader;
} public void contextDestroyed(ServletContextEvent event) {
if (this.contextLoader != null) {
this.contextLoader.closeWebApplicationContext(event.getServletContext());
} }
}

然后看下初始化WebApplicationContext

org.springframework.web.context.ContextLoader#createWebApplicationContext方法中我们可以看到如下的一段内容

protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent) throws BeansException {
Class contextClass = this.determineContextClass(servletContext);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("xxxx");
} else {
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
wac.setParent(parent);
wac.setServletContext(servletContext);
// 这里是从ServletContext对象中,获取我们配置的Spring上下文配置文件路径
wac.setConfigLocation(servletContext.getInitParameter("contextConfigLocation"));
this.customizeContext(servletContext, wac);
wac.refresh();
return wac;
}
}

通过上面的两个类,一个配置,我们对之前使用Servlet容器来启动Spring容器,就有了一个比较直观的认识。

先Spring容器后Servlet容器

接下来我们看下SpringBoot是如何启动Servlet容器的

我们启动SpringBoot一般都是如此

SpringApplication.run(Application.class, args);

static run方法中,实例化SpringApplication对象

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 决定WebApplication类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
static WebApplicationType deduceFromClasspath() {
// 如果存在类 org.springframework.web.reactive.DispatcherHandler
// 并且没有 org.springframework.web.servlet.DispatcherServlet
// 和 org.glassfish.jersey.servlet.ServletContainer
// 则认为是REACTIVE类型的WEB应用
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// 存在 javax.servlet.Servlet
// 和 org.springframework.web.context.ConfigurableWebApplicationContext
// 则认为是SERVLET类型的WEB应用
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
// 都没有出现就不是WEB应用
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}

然后在方法org.springframework.boot.SpringApplication#createApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
// 如果还没有决定好使用哪个ApplicationContext的子类,根据WebApplicationType来决定
try {
switch (this.webApplicationType) {
case SERVLET:
// 加载 AnnotationConfigServletWebServerApplicationContext类
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
// 实例化ApplicationContext对象
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

之后在org.springframework.boot.SpringApplication#run(java.lang.String...)方法中,调用org.springframework.boot.SpringApplication#refreshContext,然后调用下面的方法

protected void refresh(ApplicationContext applicationContext) {
// 类型判断
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
// 调用refresh方法 这里利用多态,调用实际对象的refresh方法
((AbstractApplicationContext) applicationContext).refresh();
}

最终会调用到org.springframework.context.support.AbstractApplicationContext#refresh

refresh方法中有一个onRefresh()

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// ... 省略
try {
// ... 省略
// Initialize other special beans in specific context subclasses.
// 在特定的上下文子类中,初始化一些特殊的Bean
onRefresh();
// ... 省略
}
catch (BeansException ex) {
// ... 省略
}
finally {
// ... 省略
}
}
}

这个onRefresh方法由子类实现,这里是org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh

protected void onRefresh() {
super.onRefresh();
try {
// 关键时刻来了,创建WebServer
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}

暂时看到这里就可以了,后面就是根据具体引入了哪个Servlet容器的jar包,来进行启动操作,以Tomcat为例

org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer

public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
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);
return getTomcatWebServer(tomcat);
}

这一步走完后,Servlet容器基本就被启动了,不过Spring容器还没有初始化完成。

总结

无论是Servlet容器先启动,还是Spring容器先启动,其实都没有关系,区别就是先后。

这两个构成了一个整体,并不是你中有我,或者我中有你的关系。

在Servlet容器启动时,或者Spring容器启动时,都会开启一个虚拟机实例进程,后面加载的代码,全部都是位于这一个虚拟机进程中,Servlet容器会负责监听一个端口,处理HTTP请求,再与我们Spring容器对接。

两种方式的启动先后顺序,并没有改变对HTTP请求的处理流程。

也可以看出,这俩的相互独立性。

Serlvet容器与Web应用的更多相关文章

  1. WEB 容器、WEB服务和应用服务器的区别与联系

    Web容器:    何为容器?    容器是一种服务调用规范框架,j2ee大量运用了容器和组件技术来构建分层的企业级应用,在J2EE规范中,相应的有Web Container和EJB Containe ...

  2. 基于纯Java代码的Spring容器和Web容器零配置的思考和实现(3) - 使用配置

    经过<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1) - 数据源与事务管理>和<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(2) - ...

  3. IOC容器在web容器中初始化——(一)两种配置方式

    参考文章http://blog.csdn.net/liuganggao/article/details/44083817,http://blog.csdn.net/u013185616/article ...

  4. 各种容器与服务器的区别与联系 Servlet容器 WEB容器 Java EE容器 应用服务器 WEB服务器 Java EE服务器

    转自:https://blog.csdn.net/tjiyu/article/details/53148174 各种容器与服务器的区别与联系 Servlet容器 WEB容器 Java EE容器 应用服 ...

  5. spring源码研究之IoC容器在web容器中初始化过程

    转载自 http://ljbal.iteye.com/blog/497314 前段时间在公司做了一个项目,项目用了spring框架实现,WEB容器是Tomct 5,虽然说把项目做完了,但是一直对spr ...

  6. HttpServlet容器响应Web客户流程

    HttpServlet容器响应Web客户请求流程如下: 1)Web客户向Servlet容器发出Http请求: 2)Servlet容器解析Web客户的Http请求: 3)Servlet容器创建一个Htt ...

  7. HttpServlet容器响应Web客户请求流程?

    1)Web客户向Servlet容器发出Http请求: 2)Servlet容器解析Web客户的Http请求: 3)Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息: ...

  8. WEB容器启动——web.xml加载详解

    最近在看spring的源码,关于web.xml文件在容器(Tomcat.JBOSS等)启动时加载顺序问题很混乱,通过搜集资料,得出以下的结论: 1.加载顺序与它们在 web.xml 文件中的先后顺序无 ...

  9. IOC容器在web容器中初始化过程——(二)深入理解Listener方式装载IOC容器方式

    先来看一下ContextServletListener的代码 public class ContextLoaderListener extends ContextLoader implements S ...

随机推荐

  1. 使用 keras 和 tfjs 构建血细胞分类模型

    欢迎大家关注我们的网站和系列教程:http://www.tensorflownews.com/,学习更多的机器学习.深度学习的知识!

  2. App压力稳定性测试之Monkey

    一.Monkey简介 Android系统自带monkey程序,模拟用户触摸屏幕.滑动Trackball.按键等操作来对设备上的程序进行压力测试,检测程序多久的时间会发生异常. Monkey的使用是在产 ...

  3. JavaScript中去除字符串空格的方法

    语法 string.trim() 参数值 无 返回值 类型:string 描述:返回移除头尾空格的字符串 技术细节 JavaScript版本: ECMAScript 5 去除字符串左右两端的空格,在v ...

  4. js Object方法小结

    1. Object.defineProperty(obj,prop,{                 value:...,                 writable:boolean,//可写 ...

  5. AttributeError: module 'tensorflow.python.keras.backend' has no attribute 'get_graph'处理办法

    原因:安装的tensorflow版本和keras版本不匹配,只需卸载keras,重新安装自己tensorflow对应的版本. Keras与tensorflow版本匹配查询网站  

  6. Java 虚拟机运行时数据区

    写在前面 本文描述的有关于 JVM 的运行时数据区是基于 HotSpot 虚拟机. 概述 JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以 ...

  7. WEB缓存系统之varnish代理以及健康状态检测配置

    前文我们聊了下varnish的缓存项修剪配置,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/12666406.html:今天我来说一下varnish作为代理服务 ...

  8. 《快速认识 Three.js 》

    此文仅作备份之用,为了更好的阅读体验,建议访问原文链接:<Three.js - 走进3D的奇妙世界.> ,感谢原作者的好文.

  9. Pytest系列(10) - firture 传参数 request的详细使用

    如果你还想从头学起Pytest,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1690628.html 前言 为了提高复用性,我们在写测试 ...

  10. 独立Web站点的快速部署

                                                                  独立Web站点的快速部署 1案例1:独立Web站点的快速部署 1.1问题 本 ...