ContextLoaderListener是否需要使用?

122
一个标准的Spring Web应用程序(由Roo或“Spring MVC Project”模板创建)将创建一个带有ContextLoaderListenerDispatcherServlet的web.xml。 为什么它们不仅使用DispatcherServlet并使其加载完整的配置呢? 我理解ContextLoaderListener应该用于加载与Web无关的内容,而DispatcherServlet用于加载与Web相关的内容(控制器等)。 这会导致两个上下文:父上下文和子上下文。
背景:
我已经按照这种标准方式做了几年。
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
</context-param>

<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Handles Spring requests -->
<servlet>
    <servlet-name>roo</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/spring/webmvc-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

这经常会导致两个上下文和它们之间的依赖关系出现问题。过去,我总是能找到解决方案,而且我强烈感觉这使软件结构/架构始终更好。但现在我面临着两个上下文事件问题

-- 然而,这让我重新考虑了这两个上下文模式,并且我在问自己:为什么要把自己带进这种麻烦中,为什么不用一个DispatcherServlet加载所有spring配置文件,并完全删除ContextLoaderListener。(我仍然需要不同的配置文件,但只有一个上下文。)

是否有任何原因不删除ContextLoaderListener


这经常会导致两个上下文和它们之间的依赖关系出现问题。这是一个很好的例子,说明我认为依赖注入框架只会让我们的生活比自己实现依赖注入更加困难。 - Andy
1
@Andy - 虽然我对这个观点有些同情,但我不禁注意到你需要同时使用上下文的用例(在安全过滤器和servlet之间共享对象,在视图渲染完成后自动管理事务以关闭它们)很难在没有框架帮助的情况下实现。这主要是因为servlet API显然从未设计用于与依赖注入一起工作,并且如果您尝试自己做,它会积极地反对您。 - Periata Breatta
@PeriataBreatta 我明白了!那么,你认为如果它被设计得不同,会有更好的替代Spring MVC的选择吗?虽然人们本来就可以设计完全替代Servlet API的方案... - Andy
@PeriataBreatta 有趣的是,在JS世界中,我已经使用Express路由HTTP请求约一年了,很少看到任何关于“依赖注入”的提及,也没有任何类似Spring框架的东西。 - Andy
3个回答

90
在您的情况下,不需要保留ContextLoaderListenerapplicationContext.xml。如果您的应用程序只需使用servlet的上下文即可正常工作,请继续使用它,这更简单。
是的,通常鼓励将非Web内容保留在Web应用程序级别的上下文中,但这只是一个薄弱的约定。
使用Web应用程序级别的上下文的唯一令人信服的原因是:
  • 如果您有多个需要共享服务的DispatcherServlet
  • 如果您有需要访问Spring-wired服务的遗留/非Spring Servlet
  • 如果您有连接到Web应用程序级别上下文的Servlet过滤器(例如Spring Security的DelegatingFilterProxy, OpenEntityManagerInViewFilter等)
这些都与您无关,因此不必增加额外的复杂性。
当向servlet的上下文添加后台任务(如定期任务,JMS连接等)时,请小心。如果忘记将<load-on-startup>添加到您的web.xml中,则这些任务将在第一次访问servlet之前不会启动。

2
关于监听器,它们好像需要由Context Loader监听器创建的上下文(IllegalStateException,找不到WebApplicationContext,由MultipartFilter、CharacterEncodingFilter、HiddenHttpMethodFilter、Spring Security DelegatingFilterProxy和OpenEntityManagerInViewFilter触发)。把它反过来做(通过ContextLoaderListener加载所有内容,让DispatcherServlet没有配置),这是一个好主意吗? - Ralph
@Ralph:干得好,我已经将该用例添加到列表中。至于不配置DispatcherServlet的情况——如果您这样做,就没有Web界面了。所有MVC内容都必须放在那里。 - skaffman
2
@skaffman 当我使用DelegatingFilterProxy和spring-security时,为什么要使用两个上下文?在我的情况下,spring-security bean和默认的spring上下文共享一些bean。所以它们也应该共享同一个上下文。或者应该将spring security bean放在默认的spring上下文之外吗? - Matthias M

10

我想分享一下我在Spring-MVC应用程序上所做的事情:

  1. we-mvc-config.xml中,我仅添加了带有@Controller注释的类:

<context:component-scan base-package="com.shunra.vcat">
    <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
  • applicationContext.xml文件中,我添加了所有其余内容:

  • <context:component-scan base-package="com.shunra.vcat">
        <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>
    

    是的,这是一个有用的模式。另一个有用的模式是将数据库处理bean放在应用程序上下文中(这些可能需要用于OpenSessionInViewFilter或类似操作),以及任何过滤器或监听器特别需要的内容(例如使用Spring Security所需的定义)。 - Periata Breatta

    10

    您也可以以另一种方式配置应用程序上下文。例如,为了使OpenEntityManagerInViewFilter正常工作。设置ContextLoaderListener,然后使用以下方式配置DispatcherServlet:

    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
    </servlet>
    

    请确保 contextConfigLocation 参数值为空。


    1
    但这种配置的优势是什么?“相反的方式”是什么意思? - Ralph
    “skaffman”提供的解决方案仅配置了Web应用程序上下文(servlet)。但是,使用这种方法会遇到在解决方案本身中详细说明的问题:“使用Web应用程序级别上下文的唯一强制性原因是:”… “如果您有Servlet过滤器钩入Web应用程序级别上下文(例如Spring Security的DelegatingFilterProxy,OpenEntityManagerInViewFilter等)”如果您想仅使用1个应用程序上下文XML文件,则我认为我的解决方案(通过ContextLoaderListener指定XML)更可取。 - Gunnar Hillert
    您能否在由Context Listener创建的上下文中使用MVC Web Controller? - Ralph
    1
    是的。您只需在由上下文监听器指定的context.xml文件中设置控制器即可。它的工作方式是DispatcherServlet将简单地加入“父应用程序上下文”(上下文监听器)。由于您将“contextConfigLocation”值留空,因此将仅使用由上下文监听器指定的context.xml文件。 - Gunnar Hillert
    虽然这是一个旧评论,但我想分享我的观察。@GunnarHillert,您建议的配置方法在Spring MVC中并没有立即生效。是的,控制器被创建为bean,但映射未设置。只有在我创建了org.springframework.web.servlet.handler.SimpleUrlHandlerMapping的bean并提供了映射后,它才起作用。我不是Spring大师,但我认为原因是DispathlerServlet不仅需要获取上下文并创建bean,还确保完成MVC相关功能(如映射)。 - dade
    1
    我认为你在上下文中漏掉了 mvc:annotation-driven/。@GunnarHillert 的解决方案对我有效。 - milbr

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接