对启动顺序的错误认识

之前一直有个观点,应用运行在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. CSS3过渡结束监听事件,清除/修改表单元素的一些默认样式

    document.querySelector('div').addEventListener('transitionEnd',function(){ console.log('过度结束') }) 如果 ...

  2. HDU - 1317 ~ SPFA正权回路的判断

    题意:有最多一百个房间,房间之间连通,到达另一个房间会消耗能量值或者增加能量值,求是否能从一号房间到达n号房间. 看数据,有定5个房间,下面有5行,第 iii 行代表 iii 号 房间的信息,第一个数 ...

  3. 初识js(第一篇)

    初识javascript js是前端中作交互控制的语言,有了它,我们的前端页面才能"活"起来.学好这么语言显得非常重要,但是存在一定难度,所以一定要认真学习,充满耐心. js书写规 ...

  4. 使用datasest属性改变样式

    使用datasest属性改变样式 传统做法 对于html中的标签我们可以自定义标签中的属性,例如给input加一个aaa属性 <input type="text" aaa=& ...

  5. 来讨论一下这些常见的 Redis 面试题

    Redis应该算面试中必问的一个知识点,但是发现很多童鞋并不熟悉这块,这篇就常见的一些问题做一些整理,有不对的地方欢迎留言指正! 1.Redis支持的数据类型? String(字符串) 格式: set ...

  6. 针对Kafka的centos系统参数优化

    TCP网络优化 sudo vim /etc/sysctl.conf vm.max_map_count=655360net.core.rmem_default=262144net.core.rmem_m ...

  7. 面试官:JavaScript 原始数据类型 Symbol 有什么用?

    以前提到 JavaScript 原始数据类型时,我们知道有Number,String,Null,Boolean,Undefined这几种.ES6 引入了新的基本数据类型Symbol和BigInt.今天 ...

  8. 一文教会你如何在 Spring 中进行集成测试,太赞了

    不得不说,测试真的是太重要了!但并不是所有的开发者都这样认为,这种感觉在我回到洛阳后尤其强烈.竟然有团队成员不经测试就把代码提交到代码库,并且是会报错的那种,我天呐,遇到这种队友我也是醉了. 我之前是 ...

  9. 1030 Travel Plan (30分)(dijkstra 具有多种决定因素)

    A traveler's map gives the distances between cities along the highways, together with the cost of ea ...

  10. Java 中为什么不能创建泛型数组?

    之前只是知道在 Java 中不能创建泛型数组,今天翻看 <Effective Java>其中对这个部分有讲解,记录一下. 现在我们假设在 Java 中可以创建泛型数组,看看可能会发生什么情 ...