Serlvet容器与Web应用
对启动顺序的错误认识
之前一直有个观点,应用运行在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应用的更多相关文章
- WEB 容器、WEB服务和应用服务器的区别与联系
Web容器: 何为容器? 容器是一种服务调用规范框架,j2ee大量运用了容器和组件技术来构建分层的企业级应用,在J2EE规范中,相应的有Web Container和EJB Containe ...
- 基于纯Java代码的Spring容器和Web容器零配置的思考和实现(3) - 使用配置
经过<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1) - 数据源与事务管理>和<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(2) - ...
- IOC容器在web容器中初始化——(一)两种配置方式
参考文章http://blog.csdn.net/liuganggao/article/details/44083817,http://blog.csdn.net/u013185616/article ...
- 各种容器与服务器的区别与联系 Servlet容器 WEB容器 Java EE容器 应用服务器 WEB服务器 Java EE服务器
转自:https://blog.csdn.net/tjiyu/article/details/53148174 各种容器与服务器的区别与联系 Servlet容器 WEB容器 Java EE容器 应用服 ...
- spring源码研究之IoC容器在web容器中初始化过程
转载自 http://ljbal.iteye.com/blog/497314 前段时间在公司做了一个项目,项目用了spring框架实现,WEB容器是Tomct 5,虽然说把项目做完了,但是一直对spr ...
- HttpServlet容器响应Web客户流程
HttpServlet容器响应Web客户请求流程如下: 1)Web客户向Servlet容器发出Http请求: 2)Servlet容器解析Web客户的Http请求: 3)Servlet容器创建一个Htt ...
- HttpServlet容器响应Web客户请求流程?
1)Web客户向Servlet容器发出Http请求: 2)Servlet容器解析Web客户的Http请求: 3)Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息: ...
- WEB容器启动——web.xml加载详解
最近在看spring的源码,关于web.xml文件在容器(Tomcat.JBOSS等)启动时加载顺序问题很混乱,通过搜集资料,得出以下的结论: 1.加载顺序与它们在 web.xml 文件中的先后顺序无 ...
- IOC容器在web容器中初始化过程——(二)深入理解Listener方式装载IOC容器方式
先来看一下ContextServletListener的代码 public class ContextLoaderListener extends ContextLoader implements S ...
随机推荐
- 一文综述python读写csv xml json文件各种骚操作
Python优越的灵活性和易用性使其成为最受欢迎的编程语言之一,尤其是对数据科学家而言.这在很大程度上是因为使用Python处理大型数据集是很简单的一件事情. 如今,每家科技公司都在制定数据战略. ...
- jmeter响应乱码(十四)
方法一: jmeter响应乱码解决方法:在jmeter的bin目录下找到jmeter.propertis这个文件,修改里面的#sampleresult.default.encoding=ISO-885 ...
- 【Pytest02】全网最全最新的Pytest框架快速进阶篇(pytest前置和后置以及忽略测试用例)
一.Pytest的前置和后置方法 1.Pytest可以集成unittest实现前置和后置 import unittest import pytest class TestCase(unittest.T ...
- docker 本地镜像导入导出 compose安装
docker 本地镜像导入导出 1.Docker导入本地gz镜像 [root@rocketmq-nameserver4 dev]# cat alibaba-rocketmq-3.2.6.tar.gz ...
- [tyvj2032]升降梯上<dp&spfa>
题目背景 开启了升降梯的动力之后,探险队员们进入了升降梯运行的那条竖直的隧道,映入眼帘的是一条直通塔顶的轨道.一辆停在轨道底部的电梯.和电梯内一杆控制电梯升降的巨大手柄. 题目描述 Nescafe 之 ...
- B [JLOI2012]树
时间限制 : - MS 空间限制 : - KB 评测说明 : 1s,128m 问题描述 在这个问题中,给定一个值S和一棵树.在树的每个节点有一个正整数,问有多少条路径的节点总和达到S.路径中节点 ...
- Light of future-冲刺Day 6
目录 1.SCRUM部分: 每个成员进度 SCRUM 会议的照片 签入记录 代码运行截图 用户浏览界面 订单详情界面 管理员浏览界面 新增后台界面 2.PM 报告: 时间表 燃尽图 任务总量变化曲线 ...
- 1008 Elevator (20 分)
The highest building in our city has only one elevator. A request list is made up with N positive nu ...
- Java对字母大小写转换
Java对字母大小写转换 1.小写——大写String aa = "abc".toUpperCase(); 2.大写——小写 String bb = "ABC" ...
- go For-range结构
For结构: for 初始化;条件语句;修饰语句{ 输出 } 一.For-range结构是可以怎么用? 这种构建方法可以应用于数组和切片: for ix, value := range slice1 ...