学习技术方面由浅入深的层次步骤: 了解:入门,如何去使用这门技术 掌握:具体,它的原理是什么 熟悉:规则实践,在理解原理的基础上,如何去模仿, 精通:解决问题 专家:觉悟,扩展创新,如何去进一步演化
在分析SpringMVC框架具体组件之前,我们先了解Spring WebApplicationContext。
一、先说ServletContext
javaee标准规定了,servlet容器需要在应用项目启动时,给应用项目初始化一个ServletContext作为公共环境容器存放公共信息。ServletContext中的信息都是由web容器提供的。
在web项目中,web.xml文件我们通常有如下配置:
主要分为两部分:
1)、DispatcherServlet的配置 2)、ContextLoaderListener的配置
这两部分的配置为Spring MVC启动的入口,也是Web容器与Spring IOC/MVC相耦合的点(通过ServletContext耦合)。其中DispatcherServlet是一个Servlet,具备Servlet的生命周期,ContextLoaderListener实现了ServletContextListener接口,该接口提供了关于ServletContext生命周期的回调方法。在Web容器启动时,将调用Servlet生命周期的init方法,同时其作为宿主环境的上下文ServletContext将触发事件信息使得ServletContextListener监听器调用contextInitialized方法。
1、ContextLoaderListner监听:
代码语言:javascript复制public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
//启动了Spring IOC容器,其配置文件位置在web.xml中已经设定
//通常这个容器中的Bean主要是web开发中的Service层和DAO层相关的类
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
2、DispatcherServlet启动
再看DispatcherServlet,在Servlet启动时将调用init方法。其继承结构如下图:
init()方法在HttpServletBean中定义,该方法调用了在FrameworkServlet中定义的initServletBean()方法,部分代码如下:
代码语言:javascript复制@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" getServletName() "'");
try {
//启动另外一个IOC容器,该IOC容器配置文件在web.xml中设定过
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
}
该IOC容器中包含的通常是web开发中的Controller层相关的Bean,启动过程中将会将ContextLoaderListener中启动的容器设置为父容器,形成IOC容器体系,在容器体系中获取Bean时,先在子容器中查找,再去父容器中查找。如Controller层中Service的注入,即需要去父容器中查找。
至此,Spring MVC启动了两个IOC容器,其中ContextLoaderListener启动的为父容器(通常负责Service层和DAO层的相关Bean管理),而DispatcherServlet启动的为子容器(通常负责Controller层的相关Bean管理),IOC容器体系建立完毕,同时两个IOC容器通过ServletContext与Web容器(Tomcat)相耦合。
HttpServletBean的init方法最终将调用DispatcherServlet的initStrategies方法,该方法主要用来初始化Spring MVC的主要支持部件:
代码语言:javascript复制protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
initStrategies中的主要工作就是设置DispatcherServlet中相关属性的值,对于handlerMapping/handlerAdapters/ViewResolvers这三者来说,都是先在IOC容器中查找是否已经有配置的各类实例,如果没有则启用默认类。默认值在DispatcherServlet.properties文件中有定义。至此,Spring MVC已经启动完毕。 可见,SpringMVC由tomcat以web.xml里一个Servlet一个Listener的配置触发启动,然后以这两个建立IOC容器体系,最终进行组件的初始化工作,启动完成。
启动Web容器,执行流程如下:
1、启动一个WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml,读两个节点: 和 <context-param></context-param>和<listener></listener> 2、紧接着,容器创建一个ServletContext(上下文),在该应用内全局共享; 3、容器将<context-param></context-param>转化为键值对,并交给ServletContext; 4、容器创建<listener></listener>中的类实例,即创建监听,该监听器必须实现自ServletContextListener接口,如ContextLoaderListener,或则自定义实现类。 5、Web项目启动中,在监听类中ontextInitialized(ServletContextEvent event)初始化方法会被执行,在该方法中获取到ServletContext和全局参数; 6、得到这个context-param的值之后,你就可以做一些操作了。这个时候你的WEB项目还没有完全启动完成,这个动作会比所有的Servlet都要早。换句话说,这个时候,你对<context-param>中的键值做的操作,将在你的WEB项目完全启动之前被执行。 7、Web项目结束时,监听类中的contextDestroyed(ServletContextEvent event)方法会被执行; 简单来说流程就是:1、读配置文件节点-->2、创建ServletContext-->3、设置参数到Context中-->4、监听listener并执行初始化方法和销毁方法。
二、Spring Web初始化应用上下文配置
web容器是制定规范并提供了基础接口,让应用程序按规范要求实现相应接口。要获取Web容器的ServletContext对象,要实现这个ServletContextListener接口。Spring为了满足Web容器规范,Spring提供了实现ServletContextListener接口的上下文初始化监听器. org.springframework.web.context.ContextLoaderListener
spring为我们提供的IOC容器,需要我们指定容器的配置文件,然后由该监听器初始化并创建该容器。要求我们项目要指定配置文件的地址及文件名称,一定要使用:contextConfigLocation作为参数名称。如下:
代码语言:javascript复制<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 配置上下文监听: start -->
<!-- 监听容器事件,初始化Web应用上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ContextLoaderListener监听器默认读取/WEB-INF/下的applicationContext.xml文件。但是通过context-param指定配置文件路径后,便会去你指定的路径下读取对应的配置文件,并进行初始化。项目启动时,便会执行类ContextLoaderListener的contextInitialized方法,创建WebApplicationContext(Web应用上下文)并以键值对形式存放与ServletContext中,可以用getInitParameter(key)方法从ServletContext的对象中取出web上下文初始化参数值,key为参数名,返回的是参数值。
三、spring初始化哪些上下文?
在web.xml中,可以配置多个Servlet,如下: 1)<context-param>标签:web.xml只能有一个<context-param>,即声明应用范围全局范围内的初始化参数。 2)servlet的<init-param>标签:一个应用程序中可以拥有多个servlet。例如可能希望以一种方式处理/ap/*请求,而以另一种方式处理/ap/*请求。对于每个servlet,您都可以有一个上下文对象,即WebApplicationContext。
代码语言:javascript复制<servlet>
<servlet-name>apiserver</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>ApiServlet</servlet-name>
<servlet-class>com.demo.servlet.ApiServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ApiServlet</servlet-name>
<url-pattern>/apiserver2/*</url-pattern>
</servlet-mapping>
1)servlet容器启动,为应用创建一个“全局上下文环境”:ServletContext 2)SpringIOC容器先根据监听初始化WebApplicationContext, 3)然后再初始化web.xml中其他配置的servlet,为其初始化自己servlet的上下文信息servletContext,并加载其设置的配置信息和参数信息到该上下文中,将WebApplicationContext设置为它的父容器。 所以最后的关系是ServletContext包含了WebApplicationContext,WebApplicationContext包含了其他的Servlet上下文环境。如下图:
对于作用范围而言,在DispatcherServlet中可以引用由ContextLoaderListener所创建的ApplicationContext中的内容,而反过来不行。当Spring在执行ApplicationContext的getBean时,如果在自己context中找不到对应的bean,则会在父ApplicationContext中去找。这也解释了为什么我们可以在DispatcherServlet中获取到由ContextLoaderListener对应的ApplicationContext中的bean。 servlet和根上下文是双管齐下webApplicationContext:
四、WebApplicationContext和ApplicationContext关系
WebApplicationContext是普通ApplicationContext的扩展,它具有Web应用程序所需的一些额外功能。
由于Web应用比一般应用拥有更多的特性,因此WebApplicationContext扩展了ApplicationContext WebApplicationContext是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化工作。 从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。
WebApplicationContext扩展了ApplicationContext.在 WebApplicationContext中定义了一个常量 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,在上下文启动时,WebApplicationContext以此为键放置在ServletContext属性列表中
代码语言:javascript复制public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
ConfigurableWebApplicationContext扩展了WebApplicationContext,它允许通过配置的方式实例化,同时设置两个重要方法 setServletContext(ServletContext context) 为spring设置web应用上下文,以便两者整合 setConfigLocations(String[]locations) 设置Spring配置的文件地址。
如果使用@Configuration的java类提供配置信息的配置 web.xml配置修改如下
代码语言:javascript复制<!--通过指定context参数,让Spring使用AnnotationConfigWebApplicationContext启动容器而非XmlWebApplicationContext
默认没配置时是使用XmlWebApplicationContext-->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!--指定标注了@Configuration的类,多个可以用逗号分隔-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.demo.contextConfigLocation</param-value>
</context-param>
<!--监听器将根据上面的配置使用AnnotationConfigWebApplicationContext
根据contextConfigLocation
指定的配置类启动Spring容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>