Spring MVCの設定でContextLoaderListenerを使う必要があるケース

従来のSpring開発者の常識では、web.xmlにて

  • ContextLoaderListenerを設定してWebアプリケーションで共通のコンテキストを読み込む
  • DispatcherServletを設定して、サーブレットごとのコンテキストを読み込む

というように2階層のアプリケーションコンテキストを読み込ませることが普通でした。この構成ではちょっとわかりにくいのですが、DispatcherServletの読み込んだコンテキストがContextLoaderListenerの読み込んだグローバルなコンテキストの子供として階層化されます。子供のコンテキストは親コンテキストにアクセス可能ですが、その逆は不可能ということにより、通常はコントローラーやDispatcherServlet固有の各種オブジェクトの設定を子供のコンテキスト側で読み込ませ、サービス層などその他のクラス親のコンテキストで読み込ませます。
このちょっと複雑な構成に対する簡易化を狙ったからでしょうか、Spring3になって、Keith Donald氏を中心にコンテキストをすべで単一のDispatcherServletで読み込ませるという方式が最近流布しています。
http://blog.springsource.com/2009/12/21/mvc-simplifications-in-spring-3-0/
実際、Spring Tool Suiteなどで自動生成可能なmvc-basicなどの雛形アプリケーションでは以下のような構成が生成されます。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- Enables clean URLs with JSP views e.g. /welcome instead of /app/welcome -->
	<filter>
		<filter-name>UrlRewriteFilter</filter-name>
		<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>UrlRewriteFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
		
	<!-- Handles all requests into the application -->
	<servlet>
		<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
				/WEB-INF/spring/*.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<!-- Maps all /app requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
		<url-pattern>/app/*</url-pattern>
	</servlet-mapping>
	
</web-app>

しかしながら、この構成方法がSpring MVCの初心者によってちょっとした混乱とトラブルの元となっているようです。この方式の最大の問題点は、起動順序により、サーブレットフィルターからアプリケーションコンテキストにアクセスできないことです。したがって、

  • Spring Securityを利用する場合
  • Open Session In Viewパターン用のフィルターを利用する場合

などのケースでは以下の例外となり、原因がわからず途方にくれてしまうという方がおられるようです。

ERROR [[Spring MVC Dispatcher Servlet]] Servlet.service() for servlet Spring MVC Dispatcher Servlet threw exception java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?

mvc-basicの構成を元に開発していくと遅かれ早かれ以上の問題にあたる可能性が高いので注意が必要です。こうなったら、従来どおり、ContextLoaderListenerを構成する方式に切り替えるしかありません。
興味深いことに、Keith Donald氏が率いるSpring Web Flowのサンプルでは大部分mvc-basicと同様の法式がとられているのですが、booking-mvcというSpring Securityを併用するサンプルでは以下のように構成されていました。

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">

	<!-- The master configuration file for this Spring web application -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			/WEB-INF/config/web-application-config.xml
		</param-value>
	</context-param>

	<!-- Enables use of HTTP methods PUT and DELETE -->
	<filter>
		<filter-name>httpMethodFilter</filter-name>
		<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>httpMethodFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<!-- Enables Spring Security -->
	<filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
	
	<!-- Loads the Spring web application context -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

  	<!-- Serves static resource content from .jar files such as spring-faces.jar -->
	<servlet>
		<servlet-name>Resources Servlet</servlet-name>
		<servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
		<load-on-startup>0</load-on-startup>
	</servlet>
		
	<!-- Map all /resources requests to the Resource Servlet for handling -->
	<servlet-mapping>
		<servlet-name>Resources Servlet</servlet-name>
		<url-pattern>/resources/*</url-pattern>
	</servlet-mapping>
	
	<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<servlet>
		<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value></param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<!-- Map all *.spring requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
		<url-pattern>/spring/*</url-pattern>
	</servlet-mapping>

	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>

</web-app>

つまり、今度は逆にDispatcherServletのコンテキストをダミーにして、全クラスを親コンテキストローダーで読み込ませるようになっています。あくまでもKeith氏はコンテキストを階層化して読み込ませることが嫌いなのでしょうか?たしかに、Flow Managed Persistenceの機能を使う場合などはSeamと同様に階層を分けずにモノリシックなコンテキストにした方がわかりやすいということはあると思いますが。ちょっと混乱しているので、Spring Sourceとしての公式なベストプラクティスを明示してほしいものです。