ContextLoaderListener oder nicht?

122

Eine Standard-Spring-Webanwendung (erstellt von Roo oder der Vorlage "Spring MVC Project") erstellt eine web.xml mit ContextLoaderListenerund DispatcherServlet. Warum verwenden sie nicht nur das DispatcherServletund machen es, um die komplette Konfiguration zu laden?

Ich verstehe, dass der ContextLoaderListener zum Laden der nicht webrelevanten Inhalte verwendet werden sollte und das DispatcherServlet zum Laden der webrelevanten Inhalte (Controller, ...). Dies führt zu zwei Kontexten: einem übergeordneten und einem untergeordneten Kontext.

Hintergrund:

Ich habe es mehrere Jahre lang auf diese übliche Weise gemacht.

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

Dies verursachte häufig Probleme mit den beiden Kontexten und den Abhängigkeiten zwischen ihnen. In der Vergangenheit konnte ich immer eine Lösung finden, und ich habe das starke Gefühl, dass dies die Softwarestruktur / -architektur immer besser macht. Aber jetzt stehe ich vor einem Problem mit den Ereignissen beider Kontexte .

- Dies lässt mich jedoch diese beiden Kontextmuster überdenken, und ich frage mich: Warum sollte ich mich in diese Schwierigkeiten bringen, warum nicht alle Federkonfigurationsdateien mit einer laden DispatcherServletund die ContextLoaderListenervollständig entfernen . (Ich werde immer noch verschiedene Konfigurationsdateien haben, aber nur einen Kontext.)

Gibt es einen Grund, das nicht zu entfernen ContextLoaderListener?

Ralph
quelle
"Dies verursachte häufig Probleme mit den beiden Kontexten und den Abhängigkeiten zwischen ihnen." Dies ist ein großartiges Beispiel dafür, wie ich denke, dass Frameworks für die Abhängigkeitsinjektion unser Leben nur schwieriger machen als die Abhängigkeitsinjektion zum Selbermachen.
Andy
1
@Andy - Obwohl ich mit dieser Sichtweise einverstanden bin, kann ich nicht umhin zu bemerken, dass die Anwendungsfälle, für die Sie beide Kontexte benötigen (gemeinsame Nutzung von Objekten zwischen Sicherheitsfiltern und Servlets, automatische Verwaltung von Transaktionen, damit diese nach der Ansicht geschlossen werden ohne die Hilfe des Frameworks sind Sie nur schwer zu erreichen. Dies liegt hauptsächlich daran, dass die Servlet-API eindeutig nie für die Arbeit mit Abhängigkeitsinjektion entwickelt wurde und aktiv gegen Sie arbeitet, wenn Sie versuchen, dies selbst zu tun.
Periata Breatta
@PeriataBreatta Ich verstehe! Denken Sie, wenn es anders gestaltet worden wäre, gäbe es bessere Alternativen zu Spring MVC? Obwohl die Leute sowieso komplette Alternativen zur Servlet-API hätten entwerfen können ...
Andy
@PeriataBreatta Es ist interessant festzustellen, dass in der JS-Welt, in der ich Express seit etwa einem Jahr zum Weiterleiten von HTTP-Anforderungen verwende, selten "Abhängigkeitsinjektion" erwähnt wird und überhaupt nichts dem Spring-Framework ähnelt.
Andy

Antworten:

86

In Ihrem Fall gibt es keinen Grund, das ContextLoaderListenerund beizubehalten applicationContext.xml. Wenn Ihre App nur mit dem Kontext des Servlets funktioniert, bleibt dies einfacher.

Ja, das allgemein empfohlene Muster besteht darin, Nicht-Web-Inhalte im Kontext auf Webanwendungsebene zu belassen, aber es ist nichts weiter als eine schwache Konvention.

Die einzigen zwingenden Gründe für die Verwendung des Kontexts auf Webanwendungsebene sind:

  • Wenn Sie mehrere haben DispatcherServlet, die Dienste gemeinsam nutzen müssen
  • Wenn Sie Legacy- / Nicht-Spring-Servlets haben, die Zugriff auf Spring-Wired-Dienste benötigen
  • Wenn Sie Servlet - Filter , dass Haken in die Webapp-Ebene Kontext (zB Spring Security ist DelegatingFilterProxy, OpenEntityManagerInViewFilterusw.)

Keines davon trifft auf Sie zu, sodass die zusätzliche Komplexität nicht gerechtfertigt ist.

Seien Sie vorsichtig, wenn Sie dem Kontext des Servlets Hintergrundaufgaben hinzufügen, z. B. geplante Aufgaben, JMS-Verbindungen usw. Wenn Sie vergessen <load-on-startup>, Ihre web.xmlAufgaben hinzuzufügen , werden diese Aufgaben erst beim ersten Zugriff auf das Servlet gestartet.

Skaffman
quelle
2
Was ist mit Listenern? Es scheint, dass sie den vom Context Loader-Listener erstellten Kontext benötigen (IllegalStateException, No WebApplicationContext gefunden, ausgelöst durch MultipartFilter, CharacterEncodingFilter, HiddenHttpMethodFilter, Spring Security DelegatingFilterProxy und OpenEntityManagerInViewFilter). Ist es eine gute Idee, es umgekehrt zu machen (Laden Sie alles mit ContextLoaderListener und lassen Sie das DispatcherServlet ohne Konfiguration)?
Ralph
@ Ralph: Guter Fang, ich habe diesen Anwendungsfall zur Liste hinzugefügt. Wie für das Verlassen DispatcherServletohne Konfiguration - wenn du das getan hast, dann würden Sie keine Web - Oberfläche haben. Alle MVC-Sachen müssen da rein.
Skaffman
2
@skaffman Warum sollte ich zwei Kontexte verwenden, wenn ich Spring-Security mit DelegatingFilterProxy verwende? In meinem Fall teilen sich Spring-Security-Beans und der Standard-Spring-Kontext einige Beans. Sie sollten also auch den gleichen Kontext haben. Oder sollten Spring Security Beans aus dem Standard-Spring-Kontext herausgehalten werden?
Matthias M
10

Sie können den Anwendungskontext auch umgekehrt konfigurieren. Zum Beispiel, damit der OpenEntityManagerInViewFilter funktioniert. Richten Sie den ContextLoaderListener ein und konfigurieren Sie Ihr DispatcherServlet mit:

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

Stellen Sie einfach sicher, dass der Parameterwert contextConfigLocation leer ist.

Gunnar Hillert
quelle
1
Aber was ist der Vorteil dieser Konfiguration? Und was meinst du mit "umgekehrt"?
Ralph
Die Lösung von "skaffman" hat nur einen Webanwendungskontext (Servlet) konfiguriert. Bei diesem Ansatz treten jedoch Probleme auf, wie in der Lösung selbst beschrieben: "Die einzigen zwingenden Gründe für die Verwendung des Kontexts auf Webapp-Ebene sind:" ... "Wenn Sie Servlet-Filter haben, die sich in den Kontext auf Webbapp-Ebene einbinden (z DelegatingFilterProxy, OpenEntityManagerInViewFilter usw. von Spring Security.
Gunnar Hillert
Können Sie MVC Web Controller in dem vom Context Listener erstellten Kontext verwenden?
Ralph
1
Ja. Sie würden Ihre Controller einfach in der vom Context Listener angegebenen Datei context.xml einrichten. Die Art und Weise, wie es funktioniert, ist, dass das DispatcherServlet einfach dem "übergeordneten Anwendungskontext" (Context Listener) beitritt. Wenn Sie den Wert "contextConfigLocation" leer lassen, wird ausschließlich die vom Context Listener angegebene Datei context.xml verwendet.
Gunnar Hillert
1
Ich denke, Sie haben <mvc: annotation-powered /> in Ihrem Kontext verpasst. @ GunnarHillert Lösung funktioniert für mich.
Milbr
10

Ich möchte mitteilen, was ich in meiner Spring-MVC-Anwendung getan habe:

  1. Auf dem habe we-mvc-config.xmlich nur die mit @Controller annotierten Klassen hinzugefügt:

    <context:component-scan base-package="com.shunra.vcat">
        <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>
  2. Zu den applicationContext.xmlDateien habe ich den Rest hinzugefügt:

    <context:component-scan base-package="com.shunra.vcat">
        <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>
Modi
quelle
Ja, das ist ein nützliches Muster. Ein weiteres nützliches Muster besteht darin, Ihre Datenbank-Handling-Beans in den Anwendungskontext zu stellen (diese werden wahrscheinlich für einen OpenSessionInViewFilter oder ähnliches benötigt), zusammen mit allem, was speziell von Filtern oder Listenern benötigt wird (z. B. Definitionen, die für die Verwendung von Spring Security erforderlich sind).
Periata Breatta