ContextLoaderListener o no?

122

Una aplicación web estándar de Spring (creada por Roo o la plantilla "Spring MVC Project") crea un web.xml con ContextLoaderListenery DispatcherServlet. ¿Por qué no solo usan DispatcherServlety hacen que cargue la configuración completa?

Entiendo que ContextLoaderListener debe usarse para cargar cosas que no son relevantes para la web y DispatcherServlet se usa para cargar cosas relevantes para la web (Controladores, ...). Y esto da como resultado dos contextos: un contexto primario y uno secundario.

Antecedentes:

Lo hice de esta manera estándar durante varios años.

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

Esto a menudo causó problemas con los dos contextos y las dependencias entre ellos. En el pasado siempre pude encontrar una solución, y tengo la fuerte sensación de que esto hace que la estructura / arquitectura del software siempre sea mejor. Pero ahora estoy enfrentando un problema con los eventos de ambos contextos .

- Sin embargo, esto me hace repensar este patrón de dos contextos, y me pregunto: ¿por qué debería meterme en este problema, por qué no cargar todos los archivos de configuración de Spring con uno DispatcherServlety eliminarlos por ContextLoaderListenercompleto? (Todavía tendré diferentes archivos de configuración, pero solo un contexto).

¿Hay alguna razón para no eliminar el ContextLoaderListener?

Ralph
fuente
"Esto a menudo causó problemas con los dos contextos y las dependencias entre ellos". Este es un gran ejemplo de cómo creo que los marcos de inyección de dependencia solo hacen que nuestras vidas sean más difíciles que la inyección de dependencia de bricolaje.
Andy
1
@Andy: aunque simpatizo con este punto de vista, no puedo evitar notar que los casos de uso para los que necesitas ambos contextos (compartir objetos entre filtros de seguridad y servlets, administrar automáticamente las transacciones para que se cierren después de la vista a los que redirige ha terminado la representación) son bastante difíciles de lograr sin la ayuda del marco. Esto se debe principalmente a que la API de servlet claramente nunca se diseñó para funcionar con la inyección de dependencia, y trabaja activamente contra usted si intenta hacerlo usted mismo.
Periata Breatta
@PeriataBreatta Ya veo! Bueno, ¿crees que si hubiera sido diseñado de manera diferente que habría mejores alternativas a Spring MVC? Aunque la gente podría haber diseñado alternativas completas a la API de Servlet de todos modos ...
Andy
@PeriataBreatta Es interesante observar que en el mundo JS, donde he estado utilizando Express para enrutar solicitudes HTTP durante aproximadamente un año, rara vez veo alguna mención de "inyección de dependencia" y nada parecido al framework Spring.
Andy

Respuestas:

86

En su caso, no, no hay razón para mantener el ContextLoaderListenery applicationContext.xml. Si su aplicación funciona bien solo con el contexto del servlet, eso se queda con eso, es más simple.

Sí, el patrón generalmente alentado es mantener cosas que no sean web en el contexto de nivel de aplicación web, pero no es más que una convención débil.

Las únicas razones convincentes para usar el contexto de nivel de aplicación web son:

  • Si tiene múltiples DispatcherServletque necesitan compartir servicios
  • Si tiene servlets heredados / que no son de Spring que necesitan acceso a servicios cableados de Spring
  • Si tiene filtros de servlet que se enganchan en el contexto de nivel de aplicación web (por ejemplo, de la primavera de Seguridad DelegatingFilterProxy, OpenEntityManagerInViewFilteretc)

Ninguno de estos se aplica a usted, por lo que la complejidad adicional no está justificada.

Solo tenga cuidado al agregar tareas en segundo plano al contexto del servlet, como tareas programadas, conexiones JMS, etc. Si olvida agregar <load-on-startup>a su web.xml, entonces estas tareas no se iniciarán hasta el primer acceso del servlet.

skaffman
fuente
2
¿Qué pasa con los oyentes? Parece que necesitan el Context creado por el oyente Context Loader (IllegalStateException, No WebApplicationContext encontrado, desencadenado por MultipartFilter, CharacterEncodingFilter, HiddenHttpMethodFilter, Spring Security DelegatingFilterProxy y OpenEntityManagerInViewFilter). ¿Es una buena idea hacerlo al revés (Cargue todo por ContextLoaderListener y deje el DispatcherServlet sin una configuración)?
Ralph
@Ralph: Buena captura, he agregado ese caso de uso a la lista. En cuanto a irse DispatcherServletsin una configuración, si lo hiciera, no tendría una interfaz web. Todas las cosas de MVC tienen que ir allí.
skaffman
2
@skaffman ¿Por qué debería usar dos contextos cuando uso spring-security con DelegatingFilterProxy? En mi caso, los beans de seguridad de primavera y el contexto de primavera predeterminado comparten algunos beans. Entonces también deberían compartir el mismo contexto. ¿O deben mantenerse los beans de seguridad de primavera fuera del contexto de primavera predeterminado?
Matthias M
10

También puede configurar el contexto de la aplicación al revés. Por ejemplo, para hacer que OpenEntityManagerInViewFilter funcione. Configure ContextLoaderListener y luego configure su DispatcherServlet con:

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

Solo asegúrese de que el valor del parámetro contextConfigLocation esté vacío.

Gunnar Hillert
fuente
1
Pero, ¿cuál es la ventaja de esta configuración? ¿Y qué quieres decir con "al revés"?
Ralph
La solución de "skaffman" configuró un contexto de aplicación web (servlet) solamente. Sin embargo, con ese enfoque, se encuentra con problemas como se detalla en la solución en sí: "Las únicas razones convincentes para usar el contexto de nivel de aplicación web son:" ... "Si tiene filtros de servlet que se enganchan en el contexto de nivel de aplicación web (p. Ej. DelegatingFilterProxy de Spring Security, OpenEntityManagerInViewFilter, etc.) "Si desea utilizar solo un archivo XML de contexto de aplicación, creo que sería preferible mi solución (especificando el XML a través de ContextLoaderListener).
Gunnar Hillert
¿puede usar MVC Web Controller en el contexto creado por el escucha de contexto?
Ralph
1
Si. Simplemente configuraría sus controladores en el archivo context.xml especificado por el escucha de contexto. La forma en que funciona es que DispatcherServlet simplemente se unirá al "contexto de la aplicación principal" (Context Listener). Cuando deje vacío el valor "contextConfigLocation", se utilizará exclusivamente el archivo context.xml especificado por Context Listener.
Gunnar Hillert
1
Creo que te perdiste <mvc: accionado por anotaciones /> en tu contexto. La solución @GunnarHillert funciona para mí.
milbr
10

Quiero compartir lo que he hecho en mi aplicación Spring-MVC:

  1. En el we-mvc-config.xmlAgregué solo las clases anotadas con @Controller:

    <context:component-scan base-package="com.shunra.vcat">
        <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>
  2. En los applicationContext.xmlarchivos agregué todo el resto:

    <context:component-scan base-package="com.shunra.vcat">
        <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>
Modi
fuente
Sí, este es un patrón útil. Otro patrón útil es simplemente colocar los beans de manejo de la base de datos en el contexto de la aplicación (es probable que sean necesarios para un OpenSessionInViewFilter o similar) junto con cualquier cosa específicamente necesaria para los filtros u oyentes (por ejemplo, definiciones necesarias para usar Spring Security).
Periata Breatta