一直以来,我们使用SpringMVC的时候习惯性都配置一个ContextLoaderListener,虽然曾经有过疑问,配置的这个监听器和Servlet究竟做了什么,但也没深究。

要说任何Web框架都离不开Servlet,它是一个容器,也是一种规范,你要和Web搞上关系,无非就是那么几种,监听器、过滤器和Servlet,最终都是为了切进ServletContext。

SpringMVC是基于Servlet的,直接配个Servlet不就行了嘛,但我没有真正这么做过。上次群里有个人说根本不需要配置监听器,他从来不配那玩意,也没啥事,于是我也实验了一把。

然后有了下面的总结:

ContextLoaderListener

启动方式:

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

在contextInitialized方法中调用initWebApplicationContext方法

/**
* Initialize the root web application context.
*/
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}

1. ServletContext中查找指定key的实例

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的实例如果存在则抛出异常,不存在则创建。

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

2. createWebApplicationContext(servletContext)

可以指定自己的contextClass 如果没有 则使用默认的:

org.springframework.web.context.support.XmlWebApplicationContext

如果是自己的需要实现ConfigurableWebApplicationContext(通过isAssignableFrom方法判断,注意和instanceof区别)

3. 配置和刷新WebApplicationContext

configureAndRefreshWebApplicationContext(cwac, servletContext);

  • wac.setServletContext(sc); 把servletContext放到上下文中持有
  • wac.setConfigLocation(configLocationParam); 利用servletContext的getInitParameter方法获取配置文件的位置。
  • wac.refresh(); 调用继承的refresh方法初始化IOC容器。

4. 把创建的根上下文放到servletContext中

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this.context);

把这个上下文放到servletContext中。

DispatcherServlet

GenericServlet中定义了一个

 public void init() throws ServletException {

 }

被HttpServlet继承 HttpServletBean又继承了HttpServlet

在init方法中调用initServletBean();

protected void initServletBean() throws ServletException {

}

利用模板方法模式,在子类FrameworkServlet中实现,然后在下面的方法中初始化WebApplicationContext:

initWebApplicationContext();

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);
}

1. 先取rootContext

WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

在这里会去servletContext中取出key为‘ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE’的WebApplicationContext作为一个rootContext

由于监听器启动创建了这个rootContext,现在可直接拿出来

2. findWebApplicationContext()

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;
}

由于attrName为null,直接返回null。

3. createWebApplicationContext(rootContext)

在没有启动监听器的情况下,就没有创建父上下文,然后依然是使用 :
org.springframework.web.context.support.XmlWebApplicationContext

ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());

4. configureAndRefreshWebApplicationContext(wac);

wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
……
wac.refresh();

使用shiro报错

如果只配置一个dispatcherServlet然后使用shiro

启动时报错:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘lifecycleBeanPostProcessor’ is defined

注意:springMVC的配置文件中包含了对lifecycleBeanPostProcessor的引用
这时候要将SpringMVC的配置文件扫描扩大到所有spring的配置文件

访问时报错:

java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?

关掉shiro配置后,不再报错,说明是shiro引起的,跟着shiro的配置,找到了一个突破口:DelegatingFilterProxy

<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}

initFilterBean()的时候调用一次findWebApplicationContext()

doFilter()时还会调用

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException { // Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
this.delegate = initDelegate(wac);
}
delegateToUse = this.delegate;
}
} // Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
} protected WebApplicationContext findWebApplicationContext() {
if (this.webApplicationContext != null) {
// the user has injected a context at construction time -> use it
if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
if (!((ConfigurableApplicationContext)this.webApplicationContext).isActive()) {
// the context has not yet been refreshed -> do so before returning it
((ConfigurableApplicationContext)this.webApplicationContext).refresh();
}
}
return this.webApplicationContext;
}
String attrName = getContextAttribute();
if (attrName != null) {
return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
}
else {
return WebApplicationContextUtils.getWebApplicationContext(getServletContext());
}
}

出问题的关键在此:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

先取attrName,如果不为null,到servletContext中取一个指定名称的WebApplicationContext。没有配置这个属性,默认都是取root webApplicationContext。

现在没有配置监听器,root webApplicationContext当然不存在,不过给它指定一个contextAttribute也许可以解决,于是我在web.xml中给shiro加了一个初始化参数:

<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.springServlet</param-value>
</init-param>

这下竟然可以跑了,但是其它问题还没验证。反正我不推荐这种方式。

关于DelegatingFilterProxy可以看这里了解下:
http://blog.csdn.net/xh16319/article/details/9771063

总结:

  1. 监听器启动和servlet启动都会initWebApplicationContext,由各自的初始化方法来触发。

  2. 两者各自的上下文最终都会保存到servletContext中,root WebApplicationContext使用固定的attrName,dispatcherServlet使用FrameworkServlet.class.getName() + “.CONTEXT.”+servletName。

  3. 最佳实践还是监听器加servlet的配置,各司其职,父上下文做核心容器,子上下文处理web相关。如果你要把springMVC换成其他框架如struts,也不会有什么影响。

  4. 监听器可以在整个webapp启动时初始化IOC容器,并在关闭时做一些处理。

  5. 不管是基于xml和注解的Application还是WebApplication,最终都是那套,调用继承自AbstractApplicationContext的refresh()方法。

引用下stackoverflow上关于是否要监听器的回答:

In your case, no, there’s no reason to keep the ContextLoaderListener and applicationContext.xml. If your app works fine with just the servlet’s context, that stick with that, it’s simpler.

Yes, the generally-encouraged pattern is to keep non-web stuff in the webapp-level context, but it’s nothing more than a weak convention.

The only compelling reasons to use the webapp-level context are:

  • If you have multiple DispatcherServlet that need to share services
  • If you have legacy/non-Spring servlets that need access to Spring-wired services
  • If you have servlet filters that hook into the webapp-level context (e.g. Spring Security’s DelegatingFilterProxy, OpenEntityManagerInViewFilter, etc)
    None of these apply to you, so the extra complexity is unwarranted.

Just be careful when adding background tasks to the servlet’s context, like scheduled tasks, JMS connections, etc. If you forget to add to your web.xml, then these tasks won’t be started until the first access of the servlet.

You need to understand the difference between Web application context and root application context .

In the web MVC framework, each DispatcherServlet has its own WebApplicationContext, which inherits all the beans already defined in the root WebApplicationContext. These inherited beans defined can be overridden in the servlet-specific scope, and new scope-specific beans can be defined local to a given servlet instance.

相关链接:

http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/mvc.html

http://www.codesenior.com/en/tutorial/Spring-ContextLoaderListener-And-DispatcherServlet-Concepts

http://bbs.csdn.net/topics/391076893?page=1

SpringMVC中为什么要配置Listener和Servlet的更多相关文章

  1. JavaEE开发之SpringMVC中的路由配置及参数传递详解

    在之前我们使用Swift的Perfect框架来开发服务端程序时,聊到了Perfect中的路由配置.而在SpringMVC中的路由配置与其也是大同小异的.说到路由,其实就是将URL映射到Java的具体类 ...

  2. 第五节 关于SpringMVC中Ajax的配置和应用[下午]

    成熟,不是学会表达,而是学会咽下,当你一点一点学会克制住很多东西,才能驾驭好人生. 还有一周,祥云19就算结算了,一个半月的相处希望,胖先生算一个合格的老师 小白,小蔡,2婷婷,小猴,小恒,小崔,小龙 ...

  3. springmvc中拦截器配置格式

    对于springmvc,有两种方式配置拦截器. 一是实现HandlerInterceptor接口,如 public class MyInterceptor1 implements HandlerInt ...

  4. spring入门(六)【springMVC中各数据源配置】

    在使用spring进行javaWeb开发的过程中,需要和数据库进行数据交换,为此要经常获取数据库连接,使用JDBC的方式获取数据库连接,使用完毕之后再释放连接,这种过程对系统资源的消耗无疑是很大的,这 ...

  5. [转]spring入门(六)【springMVC中各数据源配置】

    在使用spring进行javaWeb开发的过程中,需要和数据库进行数据交换,为此要经常获取数据库连接,使用JDBC的方式获取数据库连接,使用完毕之后再释放连接,这种过程对系统资源的消耗无疑是很大的,这 ...

  6. 【SpringMVC学习11】SpringMVC中的拦截器

    Springmvc的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理.本文主要总结一下springmvc中拦截器是如何定义的,以及测试拦截器的执行情况和使用 ...

  7. (转)SpringMVC学习(十二)——SpringMVC中的拦截器

    http://blog.csdn.net/yerenyuan_pku/article/details/72567761 SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter, ...

  8. JavaEE开发之SpringMVC中的自定义拦截器及异常处理

    上篇博客我们聊了<JavaEE开发之SpringMVC中的路由配置及参数传递详解>,本篇博客我们就聊一下自定义拦截器的实现.以及使用ModelAndView对象将Controller的值加 ...

  9. SpringMVC中实现Bean Validation(JSR 303 JSR 349 JSR 380)

    JSR 303是针对bean数据校验提出的一个规范.使用注解方式实现数据校验. 每个注解的用法这里就不多介绍,请移步JSR 303 - Bean Validation 介绍及最佳实践 笔者上面提到的J ...

随机推荐

  1. IIS---HTTP 错误 500.19 - Internal Server Error 的解决方法

    在验证IIS是否安装成功,测试了一个页面,报500.19错误 感谢:http://www.cnblogs.com/imjustice/archive/2011/04/04/2198116.html 图 ...

  2. sqlserver xml转表 及(cross apply与outer apply)

    一. 需求是需要把','分割的字符串转为表,便于做关联查询,于是发现可以通过xml转为表,如下: declare @XXX xml set @XXX = ' <v> <aa>1 ...

  3. Eclipse中 将java Gradle项目转换为web项目

    1.找到项目工作空间目录,打开.project文件,并修改文件, 修改如下:      找到:<natures> </natures>代码段,在代码段中加入如下内容并保存:   ...

  4. 【TYVJ】P1039 忠诚2

    [算法]线段树 [注意]修改或查询区间时,若区间能包含某棵子树就立即返回,否则线段树就失去了意义. #include<cstdio> #include<algorithm> u ...

  5. 【51NOD】数据流中的算法

    [算法]数学 [题解] 1.平均数:累加前缀和.//听说要向下取整? 2.中位数:双堆法,大于中位数存入小顶堆,小于中位数存入大顶堆,保证小顶堆内数字数量≥大顶堆,奇数则取小堆顶,偶数则取两堆顶/2. ...

  6. UIImageView属性---iOS-Apple苹果官方文档翻译

    本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址  //转载请注明出处--本文永久链接:http://www.cnblogs.com/C ...

  7. Go从入门到精通(持续更新)

    1.0 搭建环境 由于我们 Go官方网站 在我大天朝被和谐了,所以我们只能去 Go语言中文网 来下载了.Go的安装很简单,不像Java还要配置一大堆的东西,选择自己系统的对应版本,下载安装,像安装QQ ...

  8. Watchcow(POJ2230+双向欧拉回路+打印路径)

    题目链接:http://poj.org/problem?id=2230 题目: 题意:给你m条路径,求一条路径使得从1出发最后回到1,并满足每条路径都恰好被沿着正反两个方向经过一次. 思路:由于可以回 ...

  9. winform Textbox像百度一下实现下拉显示

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  10. Python 对象模型 -- (转)

    面向对象的纯粹性 在很久很久以前,C++还被称为面向对象语言(现在一般称为多范式通用语言),人们就对C++的面向对象的纯粹性提出了质疑,主要有以下几点: 并非所有的对象都是对象(很拗口?),比如指针本 ...