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 ...
随机推荐
- re模块——正则表达式
import re re.findall('\w','abc123_8()-=') \w:字母数字下划线 \W:非数字字母下划线 \s:空白字符 \S:非空字符 \d:整数数字 \D:非整数数字 \A ...
- Api接口幂等设计
1,Api接口幂等设计,也就是要保证数据的唯一性,不允许有重复. 例如:rpc 远程调用,因为网络延迟,出现了调用了2次的情况. 表单连续点击,出现了重复提交. 接口暴露之后,会被模拟请求工具(Jem ...
- spring @EnableAspectJAutoProxy背后的那些事(spring AOP源码赏析)
在这个注解比较流行的年代里,当我们想要使用spring 的某些功能时只需要加上一行代码就可以了,比如: @EnableAspectJAutoProxy开启AOP, @EnableTransaction ...
- python基础学习-字符串常见操作
字符串常见操作 索引 s = "abcdefg" # 字符串数据,切片后取出的数据都是字符串类型 # 从左至右取值:从0开始 # 从右向左取值:从-1开始 print(" ...
- H5的新特性
https://blog.csdn.net/weixin_42441117/article/details/80705203 1.h5新语义元素(有利于代码可读性和SEO)2.本地存储 h5提供 ...
- mpvue的toast弹窗组件-mptosat
几乎每个小程序都会用到的弹窗功能,弹窗是为了友好的提示用户目前小程序的状态.这样以来toast弹窗就成了小程序不可或缺的组件.mptosat用过,不赖的一款.下面记录以下使用方法: 介绍 mptoas ...
- 微信小程序动态修改页面标题setNavigationBarTitle
微信小程序是可以动态修改页面标题的. 首先我们来看看静态是怎么实现的 在对应页面的json文件里面加入下面代码就可以实现了 { "navigationBarTitleText": ...
- django自定义实现登录验证-更新版
django自定义实现登录验证 django内置的登录验证必须让开发者使用django内置的User模块,这会让开发者再某些方面被限制住 下面的模块是我自己自定义实现的django验证,使用方式和dj ...
- tp6源码解析-第二天,ThinkPHP6编译模板流程详解,ThinkPHP6模板源码详解
TP6源码解析,ThinkPHP6模板编译流程详解 前言:刚开始写博客.如果觉得本篇文章对您有所帮助.点个赞再走也不迟 模板编译流程,大概是: 先获取到View类实例(依赖注入也好,通过助手函数也好) ...
- javascript入门 之 Ajax(一)
1.在项目的根目录下创建data目录,data目录下创建info文件,编写info文件如下代码: <h1>all data<h2> <p>this is the d ...