做项目时碰到Controller不能使用aop进行拦截,从网上搜索得知:使用spring mvc 启动了两个context:applicationContext 和WebapplicationContext。
首先我们来了解applicationContext 和WebapplicationContext区别和联系吧
1. ApplicationContext和WebApplicationContext是继承关系
/** * Interface to provide configuration for a web application. This is read-only while * the application is running, but may be reloaded if the implementation supports this. * * <p>This interface adds a {@code getServletContext()} method to the generic * ApplicationContext interface, and defines a well-known application attribute name * that the root context must be bound to in the bootstrap process. * * <p>Like generic application contexts, web application contexts are hierarchical. * There is a single root context per application, while each servlet in the application * (including a dispatcher servlet in the MVC framework) has its own child context. * * <p>In addition to standard application context lifecycle capabilities, * WebApplicationContext implementations need to detect {@link ServletContextAware} * beans and invoke the {@code setServletContext} method accordingly.*/ public interface WebApplicationContext extends ApplicationContext {}
2. ContextLoaderListener 创建基于web的应用根applicationContext, 并将applicationContext放入到ServletContext, applicationContext加载或者卸载spring管理的beans。在structs和spring mvc的控制层都是这样使用的。
3. DispatcherServlet创建自己的WebApplicationContext并管理这个WebApplicationContext里面的 handlers/controllers/view-resolvers.
4. 当ContextLoaderListener和DispatcherServlet一起使用时, ContextLoaderListener 先创建一个根applicationContext,然后DispatcherSerlvet创建一个子applicationContext并且绑定到根applicationContext。
首先来看看web.xml的定义:
一般的web应用,通过ContextLoaderListener监听,ContextLoaderListener中加载的context成功后,spring 将 applicationContext存放在ServletContext的key值为"org.springframework.web.context.WebApplicationContext.ROOT"的attribute中。
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:conf/applicationContext*.xml</param-value> </context-param>
DispatcherServlet加载的context成功后,会将applicationContext存放在org.springframework.web.servlet.FrameworkServlet.CONTEXT. + (servletName)的attribute中。
<servlet> <servlet-name>mvcServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:conf/spring-dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
当然,如果没有指定*servlet.xml 配置,则默认使用DispatcherServlet的默认配置DispatcherServlet.properties
# Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
简单的来说:spring bean的管理在applicationContext中,ContextLoaderListener的作用:
1. 将applicationContext的生命周期和servletContext的生命周期联系到一起。
2. 自动管理applicationContext的创建,ContextLoaderListener 给我们提供了一个便利,不用显式的去创建applicationContext。
DispatcherServlet 相关bean的管理在WebApplicationContext,
ServletContextListener创建WebApplicationContext,WebApplicationContext可以访问ServletContext/
ServletContextAware这些bean,还可以访问getServletContext方法。
正式的官方文档:
在一个web应用中可以使用多个DispatcherServlet,每个servlet通过自己的命名空间来获取自己的webapplicationContext,然后加载此applicationContext里面的hangdlerMapping,hangdlerAdapter等等。ContextLoaderListener加载根application,所有子applicationContext共享根applicationContext。
从版本3.1 后,spring 支持将DispatcherServlet注入到根applicationContext,而不用创建自己的webapplicationContext,这主要为支持servlet 3.0 以上版本环境的要求,因为servlet 3.0 以上版本支持使用编程的方式来注册servlet实例。
spring支持使用多层层次applicationContext,通常我们使用两层结构就够了。
接下来,通过深入源代码层来探究WebApplicationContext是如何创建的?
1. DispatcherServlet的初始化过程使用到applicationContext。
我们知道DispatcherServlet直接继承自FrameworkServlet,而FrameworkServlet又继承了HttpServletBean和 ApplicationContextAware。
2. FrameworkServlet实现了ApplicationContextAware接口的setApplicationContext()方法,可知DispatcherServlet的applicationContext来自FrameworkServlet。
/** * Called by Spring via {@link ApplicationContextAware} to inject the current * application context. This method allows FrameworkServlets to be registered as * Spring beans inside an existing {@link WebApplicationContext} rather than * {@link #findWebApplicationContext() finding} a * {@link org.springframework.web.context.ContextLoaderListener bootstrapped} context. * <p>Primarily added to support use in embedded servlet containers. * @since 4.0 */ @Override public void setApplicationContext(ApplicationContext applicationContext) { if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) { this.webApplicationContext = (WebApplicationContext) applicationContext; this.webApplicationContextInjected = true; } }
3. FrameworkServlet的setApplicationContext()方法中WebApplicationContext是如何实例化的呢?
FrameworkServlet继承自HttpServletBean,HttpServletBean的初始化方法:
/** * Map config parameters onto bean properties of this servlet, and * invoke subclass initialization. * @throws ServletException if bean properties are invalid (or required * properties are missing), or if subclass initialization fails. */ @Override public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters.try { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } // Let subclasses do whatever initialization they like. initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }
FrameworkServlet 实现了initServletBean();
/** * Overridden method of {@link HttpServletBean}, invoked after any bean properties * have been set. Creates this servlet's WebApplicationContext. */ @Override protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); try { 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; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }
最终追踪到FrameworkServlet 的initWebApplicationContext()方法
/** * Initialize and publish the WebApplicationContext for this servlet. * <p>Delegates to {@link #createWebApplicationContext} for actual creation * of the context. Can be overridden in subclasses. * @return the WebApplicationContext instance * @see #FrameworkServlet(WebApplicationContext) * @see #setContextClass * @see #setContextConfigLocation */protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etcif (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. onRefresh(wac); } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }
我们来分析整个流程吧:
1. 获取根applicationContext。
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); /** * Find the root {@link WebApplicationContext} for this web app, typically * loaded via {@link org.springframework.web.context.ContextLoaderListener}. * <p>Will rethrow an exception that happened on root context startup, * to differentiate between a failed context startup and no context at all. * @param sc ServletContext to find the web application context for * @return the root WebApplicationContext for this web app, or {@code null} if none * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE */public static WebApplicationContext getWebApplicationContext(ServletContext sc) { return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); } /** * Find a custom {@link WebApplicationContext} for this web app. * @param sc ServletContext to find the web application context for * @param attrName the name of the ServletContext attribute to look for * @return the desired WebApplicationContext for this web app, or {@code null} if none */public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) { Assert.notNull(sc, "ServletContext must not be null"); Object attr = sc.getAttribute(attrName); if (attr == null) { return null; } if (attr instanceof RuntimeException) { throw (RuntimeException) attr; } if (attr instanceof Error) { throw (Error) attr; } if (attr instanceof Exception) { throw new IllegalStateException((Exception) attr); } if (!(attr instanceof WebApplicationContext)) { throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr); } return (WebApplicationContext) attr; }
2. 判断webapplicationContext是否存在?存在的话就重利用该webapplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available informationif (this.contextId != null) { wac.setId(this.contextId); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName()); } } wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); wac.refresh(); }
不存在的话,根据配置的属性名去查询:
/** * Retrieve a {@code WebApplicationContext} from the {@code ServletContext} * attribute with the {@link #setContextAttribute configured name}. The * {@code WebApplicationContext} must have already been loaded and stored in the * {@code ServletContext} before this servlet gets initialized (or invoked). * <p>Subclasses may override this method to provide a different * {@code WebApplicationContext} retrieval strategy. * @return the WebApplicationContext for this servlet, or {@code null} if not found * @see #getContextAttribute() */protected WebApplicationContext findWebApplicationContext() { String attrName = getContextAttribute(); if (attrName == null) { return null; } WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } return wac; }
3. 如果还查询不到WebapplicationContext,那么就创建一个新的WebapplicationContext,并绑定到root WebapplicationContext上:
/** * Instantiate the WebApplicationContext for this servlet, either a default * {@link org.springframework.web.context.support.XmlWebApplicationContext} * or a {@link #setContextClass custom context class}, if set. * <p>This implementation expects custom contexts to implement the * {@link org.springframework.web.context.ConfigurableWebApplicationContext} * interface. Can be overridden in subclasses. * <p>Do not forget to register this servlet instance as application listener on the * created context (for triggering its {@link #onRefresh callback}, and to call * {@link org.springframework.context.ConfigurableApplicationContext#refresh()} * before returning the context instance. * @param parent the parent ApplicationContext to use, or {@code null} if none * @return the WebApplicationContext for this servlet * @see org.springframework.web.context.support.XmlWebApplicationContext */protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "'" + ", using parent context [" + parent + "]"); } if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); wac.setConfigLocation(getContextConfigLocation()); configureAndRefreshWebApplicationContext(wac); return wac; }
4. 将子applicationContext发布到servlet context上。
if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } }
/**
* Return the ServletContext attribute name for this servlet's WebApplicationContext.
* <p>The default implementation returns
* {@code SERVLET_CONTEXT_PREFIX + servlet name}.
* @see #SERVLET_CONTEXT_PREFIX
* @see #getServletName
*/
public String getServletContextAttributeName() {
return SERVLET_CONTEXT_PREFIX + getServletName();
}
/**
* Prefix for the ServletContext attribute for the WebApplicationContext.
* The completion is the servlet name.
*/
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
最后,ContextLoaderListener启动时如何产生applicationContext呢?
见参考我的这篇文章:http://www.cnblogs.com/davidwang456/archive/2013/03/12/2956125.html
小结:
我们就以FrameworkServlet的官方说明来结束吧。没有比它更合适的!希望你喜欢。
公共抽象类FrameworkServlet继承HttpServletBean;Spring的web框架实现ApplicationContextAwareBase servlet。在一个以java为基础的整体解决方案中提供与Spring应用程序上下文的集成。
这个类提供以下功能:
管理每个servlet的WebApplicationContext实例。servlet的配置由servlet的名称空间中的bean决定。
在请求处理上发布事件,不管请求是否成功处理。子类必须实现doService(javax.servlet.http。HttpServletRequest,javax.servlet。
参考资料:
http://starscream.iteye.com/blog/1107036
http://www.softwarevol.com/en/tutorial/Spring-ContextLoaderListener-And-DispatcherServlet-Concepts