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

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

原文地址:https://www.cnblogs.com/heartlake/p/12779521.html