Servlet规范定义了一个API标准,这一标准的实现通常称为Servlet容器,比如开源的Tomcat、JBoss。
web容器更准确的说应该叫web服务器,它是来管理和部署 web应用的。web容器最典型的就是tomcat了,Tomcat是web容器也是servlet容器。所谓容器(服务器、中间件等),就是提供一些底层的、业务无关的基本功能,为真正的Servlet提供服务。
简单来说:容器负责根据请求的信息找到对应的Servlet,传递Request和Response参数,调用Servlet的service方法,完成请求的响应。
ServletContext:
javaee标准规定了,Servlet容器(如Tomcat)需要在Web应用项目启动时,给Web应用项目初始化一个全局的上下文环境,这个全局的上下文环境就是ServletContext,ServletContext实例包含了所有servlet共享的资源信息。ServletContext中的信息都是由容器提供的,通常是配置web.xml。
spring启动过程及上下文WebApplicationContext
-
首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
-
其次,在web.xml中会提供有ContextLoaderListener,如上图所示实现了ServletContextListener并继承了ContextLoader类。
ServletContextListener是Java EE标准接口之一,类似tomcat,jetty的Web容器启动时便会触发该接口的contextInitialized(ServletContextEvent event)方法,通知说Web容器初始化正在进行,在停止的时候会触发该接口的contextDestroyed(ServletContextEvent event)方法,通知说ServletContext上下文即将关闭。具体看博文ServletContextListener
ContextLoader是对根应用程序上下文执行实际的初始化工作,由ContextLoaderListener调用。具体看博文ContextLoader
ContextLoaderListener像桥梁一样连接了容器启动与Spring上下文初始化。在web容器启动时,会触发容器初始化事件,此时ContextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口,其实际的实现类是XmlWebApplicationContext,这个就是spring的IOC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。
在这个IOC容器会加载配置文件中的bean定义,初始化完毕后,spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
为属性Key,将其存储到ServletContext中,便于获取,如下图所示
在tomcat启动时也打印出来了上面的信息
[DEBUG] 2019-11-09 03:18:58,899 [localhost-startStop-1] org.springframework.web.context.ContextLoader - Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
扩展:Sping自带了多种类型的上下文,XmlWebApplicationContext是其中的一个,是从Web应用下的一个或多个xml配置文件中加载上下文定义。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
注意:该监听器,默认读取/WEB-INF/下的applicationContext.xml文件。但是通过context-param指定配置文件路径后,便会去你指定的路径下读取对应的配置文件,并进行初始化。
contextLoaderListener要加载应用中的其他bean,这些bean通常是驱动应用后端的中间层和数据层组件。
SpringMVC上下文
-
再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IOC上下文,用以持有spring mvc相关的bean。
<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
在建立DispatcherServlet自己的IOC上下文时,会利用WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这样在SpringMVC中的Controller这些bean就可以注入Spring容器中的bean。
DispatcherServlet的初始化构造器如下
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是XmlWebApplicationContext。
/**
* 初始化此servlet使用的策略对象
* @param context
*/
protected void initStrategies(ApplicationContext context) {
/**
* 初始化此类使用的MultipartResolver。 如果在BeanFactory中没有为此名称空间定义给定名称的bean,则不提供任何多部分处理。
* 我们通常会在我们的SpringMVC配置文件中对此Resolver进行配置
*
*/
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
/**
* 初始化此类使用的HandlerMappings。 如果BeanFactory中没有为此名称空间定义HandlerMapping
* Bean,则默认为BeanNameUrlHandlerMapping。
*/
initHandlerMappings(context);
initHandlerAdapters(context);
/**
* 初始化此类使用的HandlerExceptionResolver。
* 如果在BeanFactory中没有为该名称空间定义给定名称的bean,我们默认不使用任何异常解析器。
* 我们通常会在我们的SpringMVC配置文件中对此Resolver进行配置我们自定义的异常处理类来进行统一异常处理
*/
initHandlerExceptionResolvers(context);
/**
* 初始化此servlet实例使用的RequestToViewNameTranslator。
* 如果未配置任何实现,则默认为DefaultRequestToViewNameTranslator。
*/
initRequestToViewNameTranslator(context);
/**
* 初始化此类使用的ViewResolver。 如果在BeanFactory中没有为此名称空间定义ViewResolver
* bean,则默认为InternalResourceViewResolver。
* 我们通常会在我们的SpringMVC配置文件中配置我们的视图解析器的相关信息,如jsp,html
*/
initViewResolvers(context);
initFlashMapManager(context);
}
初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。
同样在tomcat启动时也打印出了初始化SpringMVC的信息,如下
[DEBUG] 2019-11-09 03:19:03,245 [localhost-startStop-1] org.springframework.web.servlet.DispatcherServlet - Published WebApplicationContext of servlet 'springMvc' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.springMvc]
从上面的初始化中也可以看到DispatcherServlet中主要是控制器、视图解析器、处理器映射这些Bean。
这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
参考:spring中DispatcherServlet、WebApplicationContext、ServletContext之间的关系