对启动顺序的错误认识

之前一直有个观点,应用运行在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. re模块——正则表达式

    import re re.findall('\w','abc123_8()-=') \w:字母数字下划线 \W:非数字字母下划线 \s:空白字符 \S:非空字符 \d:整数数字 \D:非整数数字 \A ...

  2. Api接口幂等设计

    1,Api接口幂等设计,也就是要保证数据的唯一性,不允许有重复. 例如:rpc 远程调用,因为网络延迟,出现了调用了2次的情况. 表单连续点击,出现了重复提交. 接口暴露之后,会被模拟请求工具(Jem ...

  3. spring @EnableAspectJAutoProxy背后的那些事(spring AOP源码赏析)

    在这个注解比较流行的年代里,当我们想要使用spring 的某些功能时只需要加上一行代码就可以了,比如: @EnableAspectJAutoProxy开启AOP, @EnableTransaction ...

  4. python基础学习-字符串常见操作

    字符串常见操作 索引 s = "abcdefg" # 字符串数据,切片后取出的数据都是字符串类型 # 从左至右取值:从0开始 # 从右向左取值:从-1开始 print(" ...

  5. H5的新特性

    https://blog.csdn.net/weixin_42441117/article/details/80705203 1.h5新语义元素(有利于代码可读性和SEO)2.本地存储    h5提供 ...

  6. mpvue的toast弹窗组件-mptosat

    几乎每个小程序都会用到的弹窗功能,弹窗是为了友好的提示用户目前小程序的状态.这样以来toast弹窗就成了小程序不可或缺的组件.mptosat用过,不赖的一款.下面记录以下使用方法: 介绍 mptoas ...

  7. 微信小程序动态修改页面标题setNavigationBarTitle

    微信小程序是可以动态修改页面标题的. 首先我们来看看静态是怎么实现的 在对应页面的json文件里面加入下面代码就可以实现了 { "navigationBarTitleText": ...

  8. django自定义实现登录验证-更新版

    django自定义实现登录验证 django内置的登录验证必须让开发者使用django内置的User模块,这会让开发者再某些方面被限制住 下面的模块是我自己自定义实现的django验证,使用方式和dj ...

  9. tp6源码解析-第二天,ThinkPHP6编译模板流程详解,ThinkPHP6模板源码详解

    TP6源码解析,ThinkPHP6模板编译流程详解 前言:刚开始写博客.如果觉得本篇文章对您有所帮助.点个赞再走也不迟 模板编译流程,大概是: 先获取到View类实例(依赖注入也好,通过助手函数也好) ...

  10. javascript入门 之 Ajax(一)

    1.在项目的根目录下创建data目录,data目录下创建info文件,编写info文件如下代码: <h1>all data<h2> <p>this is the d ...