java web应用容器容器_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中做如下配置

contextConfigLocation

classpath:applicationContext.xml

org.springframework.web.context.ContextLoaderListener

在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请求的处理流程。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值