Spring Web Contexts

Spring Web-Kontexte

1. Einführung

Wenn Sie Spring in einer Webanwendung verwenden, haben Sie verschiedene Möglichkeiten, die Anwendungskontexte zu organisieren, die alles miteinander verbinden.

In diesem Artikel werden die häufigsten Optionen, die Spring bietet, analysiert und erläutert.

2. Der Stamm-Webanwendungskontext

Jeder Spring-Webanwendung ist ein Anwendungskontext zugeordnet, der an ihren Lebenszyklus gebunden ist: der Root-Webanwendungskontext.

Dies ist eine alte Funktion vor Spring Web MVC, daher ist sie nicht speziell an eine Webframework-Technologie gebunden.

Der Kontext wird beim Start der Anwendung gestartet und beim Beenden dank eines Servlet-Kontext-Listeners zerstört. Die gängigsten Arten von Kontexten können auch zur Laufzeit aktualisiert werden, obwohl nicht alleApplicationContext-Implementierungen über diese Funktion verfügen.

Der Kontext in einer Webanwendung ist immer eine Instanz vonWebApplicationContext. Dies ist eine Schnittstelle, dieApplicationContext mit einem Vertrag für den Zugriff aufServletContext erweitert.

Auf jeden Fall sollten Anwendungen normalerweise nicht über die Implementierungsdetails besorgt sein:the root web application context is simply a centralized place to define shared beans.

2.1. DieContextLoaderListener

Der im vorherigen Abschnitt beschriebene Root-Webanwendungskontext wird von einem Listener der Klasseorg.springframework.web.context.ContextLoaderListener verwaltet, der Teil des Modulsspring-web ist.

By default, the listener will load an XML application context from /WEB-INF/applicationContext.xml. Diese Standardeinstellungen können jedoch geändert werden. Wir können beispielsweise Java-Annotationen anstelle von XML verwenden.

Wir können diesen Listener entweder im Webapp-Deskriptor (web.xml Datei) oder programmgesteuert in Servlet 3.x-Umgebungen konfigurieren.

In den folgenden Abschnitten werden wir uns jede dieser Optionen im Detail ansehen.

2.2. Verwenden vonweb.xml und eines XML-Anwendungskontexts

Bei Verwendung vonweb.xml konfigurieren wir den Listener wie folgt:


    
        org.springframework.web.context.ContextLoaderListener
    

Mit dem ParametercontextConfigLocationkönnen wir einen alternativen Speicherort für die XML-Kontextkonfiguration angeben:


    contextConfigLocation
    /WEB-INF/rootApplicationContext.xml

Oder mehr als ein Ort, durch Kommas getrennt:


    contextConfigLocation
    /WEB-INF/context1.xml, /WEB-INF/context2.xml

Wir können sogar Muster verwenden:


    contextConfigLocation
    /WEB-INF/*-context.xml

In jedem Fallonly one context is defined, durch Kombinieren aller Bean-Definitionen, die von den angegebenen Speicherorten geladen wurden.

2.3. Verwenden vonweb.xml und eines Java-Anwendungskontexts

Wir können neben dem XML-basierten Standardkontext auch andere Arten von Kontexten angeben. Sehen wir uns zum Beispiel an, wie Sie stattdessen die Java-Annotationskonfiguration verwenden.

Wir verwenden den ParametercontextClass, um dem Listener mitzuteilen, welche Art von Kontext instanziiert werden soll:


    contextClass
    
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    

Every type of context may have a default configuration location. In unserem Fall hatAnnotationConfigWebApplicationContext keine, daher müssen wir sie bereitstellen.

Wir können also eine oder mehrere annotierte Klassen auflisten:


    contextConfigLocation
    
        com.example.contexts.config.RootApplicationConfig,
        com.example.contexts.config.NormalWebAppConfig
    

Oder wir können den Kontext anweisen, ein oder mehrere Pakete zu scannen:


    contextConfigLocation
    org.example.bean.config

Und natürlich können wir beide Optionen kombinieren.

2.4. Programmatische Konfiguration mit Servlet 3.x.

Version 3 of the Servlet API has made configuration through the web.xml file completely optional. Bibliotheken können ihre Webfragmente bereitstellen. Hierbei handelt es sich um Teile der XML-Konfiguration, mit denen Listener, Filter, Servlets usw. registriert werden können.

Außerdem haben Benutzer Zugriff auf eine API, mit der jedes Element einer servletbasierten Anwendung programmgesteuert definiert werden kann.

Dasspring-web-Modul nutzt diese Funktionen und bietet seine API an, um Komponenten der Anwendung beim Start zu registrieren.

Spring durchsucht den Klassenpfad der Anwendung nach Instanzen derorg.springframework.web.WebApplicationInitializer-Klasse. Dies ist eine Schnittstelle mit einer einzelnen Methode,void onStartup(ServletContext servletContext) throws ServletException, die beim Start der Anwendung aufgerufen wird.

Schauen wir uns nun an, wie wir mit dieser Funktion dieselben Arten von Root-Webanwendungskontexten erstellen können, die wir zuvor gesehen haben.

2.5. Verwenden von Servlet 3.x und eines XML-Anwendungskontexts

Beginnen wir mit einem XML-Kontext, genau wie in Abschnitt 2.2.

Wir implementieren die oben genannteonStartup-Methode:

public class ApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext)
      throws ServletException {
        //...
    }
}

Lassen Sie uns die Implementierung Zeile für Zeile aufteilen.

Wir erstellen zunächst einen Stammkontext. Da wir XML verwenden möchten, muss es sich um einen XML-basierten Anwendungskontext handeln. Da wir uns in einer Webumgebung befinden, müssen auchWebApplicationContextimplementiert werden.

Die erste Zeile ist daher die explizite Version des ParameterscontextClass, auf den wir zuvor gestoßen sind, mit der wir entscheiden, welche spezifische Kontextimplementierung verwendet werden soll:

XmlWebApplicationContext rootContext = new XmlWebApplicationContext();

Dann teilen wir in der zweiten Zeile dem Kontext mit, woher die Bean-Definitionen geladen werden sollen. Wiederum istsetConfigLocations das programmatische Analogon des ParameterscontextConfigLocation inweb.xml:

rootContext.setConfigLocations("/WEB-INF/rootApplicationContext.xml");

Schließlich erstellen wir einContextLoaderListener mit dem Stammkontext und registrieren es im Servlet-Container. Wie wir sehen können, hatContextLoaderListener einen geeigneten Konstruktor, derWebApplicationContext nimmt und es der Anwendung zur Verfügung stellt:

servletContext.addListener(new ContextLoaderListener(rootContext));

2.6. Verwenden von Servlet 3.x und eines Java-Anwendungskontexts

Wenn wir einen annotationsbasierten Kontext verwenden möchten, können wir das Code-Snippet im vorherigen Abschnitt ändern, damit es stattdessen einAnnotationConfigWebApplicationContext instanziiert.

Sehen wir uns jedoch einen spezielleren Ansatz an, um das gleiche Ergebnis zu erzielen.

The WebApplicationInitializer class that we’ve seen earlier is a general-purpose interface. Es stellt sich heraus, dass Spring einige spezifischere Implementierungen bereitstellt, einschließlich einer abstrakten Klasse namensAbstractContextLoaderInitializer.

Wie der Name schon sagt, besteht seine Aufgabe darin, einContextLoaderListener zu erstellen und es im Servlet-Container zu registrieren.

Wir müssen nur sagen, wie der Root-Kontext aufgebaut werden soll:

public class AnnotationsBasedApplicationInitializer
  extends AbstractContextLoaderInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext rootContext
          = new AnnotationConfigWebApplicationContext();
        rootContext.register(RootApplicationConfig.class);
        return rootContext;
    }
}

Hier können wir sehen, dass wir dieContextLoaderListener nicht mehr registrieren müssen, was uns ein bisschen Boilerplate-Code erspart.

Beachten Sie auch die Verwendung derregister-Methode, die fürAnnotationConfigWebApplicationContext spezifisch ist, anstelle der allgemeinerensetConfigLocations: Durch Aufrufen können wir einzelne mit Anmerkungen versehene@Configuration-Klassen im Kontext registrieren Dadurch wird das Scannen von Paketen vermieden.

3. Dispatcher-Servlet-Kontexte

Konzentrieren wir uns nun auf eine andere Art von Anwendungskontext. Dieses Mal beziehen wir uns auf eine Funktion, die für Spring MVC spezifisch ist, und nicht auf die allgemeine Unterstützung von Spring-Webanwendungen.

Spring MVC applications have at least one Dispatcher Servlet configured (aber möglicherweise mehr als eine, wir werden später über diesen Fall sprechen). Dies ist das Servlet, das eingehende Anforderungen empfängt, diese an die entsprechende Controllermethode sendet und die Ansicht zurückgibt.

Each DispatcherServlet has an associated application context. In solchen Kontexten definierte Beans konfigurieren das Servlet und definieren MVC-Objekte wie Controller und View Resolver.

Lassen Sie uns zuerst sehen, wie Sie den Kontext des Servlets konfigurieren. Wir werden uns später mit einigen detaillierten Details befassen.

3.1. Verwenden vonweb.xml und eines XML-Anwendungskontexts

DispatcherServlet wird normalerweise inweb.xml mit einem Namen und einer Zuordnung deklariert:


    normal-webapp
    
        org.springframework.web.servlet.DispatcherServlet
    
    1


    normal-webapp
    /api/*

Wenn nicht anders angegeben, wird der Name des Servlets verwendet, um die zu ladende XML-Datei zu bestimmen. In unserem Beispiel verwenden wir die DateiWEB-INF/normal-webapp-servlet.xml.

Wir können auch einen oder mehrere Pfade zu XML-Dateien angeben, ähnlich wie beiContextLoaderListener:


    ...
    
        contextConfigLocation
        /WEB-INF/normal/*.xml
    

3.2. Verwenden vonweb.xml und eines Java-Anwendungskontexts

Wenn wir einen anderen Kontexttyp verwenden möchten, gehen wir wieder wie beiContextLoaderListener vor. Das heißt, wir geben einencontextClass-Parameter zusammen mit einem geeignetencontextConfigLocation an:


    normal-webapp-annotations
    
        org.springframework.web.servlet.DispatcherServlet
    
    
        contextClass
        
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        
    
    
        contextConfigLocation
        com.example.contexts.config.NormalWebAppConfig
    
    1

3.3. Verwenden von Servlet 3.x und eines XML-Anwendungskontexts

Wir werden uns erneut zwei verschiedene Methoden zum programmgesteuerten Deklarieren vonDispatcherServlet ansehen und eine auf einen XML-Kontext und die andere auf einen Java-Kontext anwenden.

Beginnen wir also mit einem generischenWebApplicationInitializer und einem XML-Anwendungskontext.

Wie wir bereits gesehen haben, müssen wir dieonStartup-Methode implementieren. Dieses Mal erstellen und registrieren wir jedoch auch ein Dispatcher-Servlet:

XmlWebApplicationContext normalWebAppContext = new XmlWebApplicationContext();
normalWebAppContext.setConfigLocation("/WEB-INF/normal-webapp-servlet.xml");
ServletRegistration.Dynamic normal
  = servletContext.addServlet("normal-webapp",
    new DispatcherServlet(normalWebAppContext));
normal.setLoadOnStartup(1);
normal.addMapping("/api/*");

Wir können leicht eine Parallele zwischen dem obigen Code und den entsprechendenweb.xml-Konfigurationselementen ziehen.

3.4. Verwenden von Servlet 3.x und eines Java-Anwendungskontexts

Dieses Mal konfigurieren wir einen auf Anmerkungen basierenden Kontext mithilfe einer speziellen Implementierung vonWebApplicationInitializer:AbstractDispatcherServletInitializer.

Dies ist eine abstrakte Klasse, die es uns nicht nur ermöglicht, einen Root-Webanwendungskontext wie zuvor zu erstellen, sondern auch ein Dispatcher-Servlet mit minimalem Boilerplate zu registrieren:

@Override
protected WebApplicationContext createServletApplicationContext() {

    AnnotationConfigWebApplicationContext secureWebAppContext
      = new AnnotationConfigWebApplicationContext();
    secureWebAppContext.register(SecureWebAppConfig.class);
    return secureWebAppContext;
}

@Override
protected String[] getServletMappings() {
    return new String[] { "/s/api/*" };
}

Hier sehen wir eine Methode zum Erstellen des mit dem Servlet verknüpften Kontexts, genau wie wir es zuvor für den Stammkontext gesehen haben. Außerdem haben wir eine Methode, um die Zuordnungen des Servlets wie inweb.xml anzugeben.

4. Übergeordnete und untergeordnete Kontexte

Bisher haben wir zwei Haupttypen von Kontexten gesehen: den Root-Webanwendungskontext und den Dispatcher-Servlet-Kontext. Dann könnten wir eine Frage haben:are those contexts related?

Es stellt sich heraus, dass dies der Fall ist. Tatsächlichthe root context is the parent of every dispatcher servlet context. Somit sind im Kontext der Root-Webanwendung definierte Beans für jeden Dispatcher-Servlet-Kontext sichtbar, nicht jedoch umgekehrt.

In der Regel wird der Stammkontext zum Definieren von Service-Beans verwendet, während der Dispatcher-Kontext die Beans enthält, die sich speziell auf MVC beziehen.

Beachten Sie, dass wir auch Möglichkeiten gesehen haben, den Dispatcher-Servlet-Kontext programmgesteuert zu erstellen. Wenn wir das übergeordnete Element manuell festlegen, setzt Spring unsere Entscheidung nicht außer Kraft, und dieser Abschnitt ist nicht mehr gültig.

In einfacheren MVC-Anwendungen ist es ausreichend, einen einzigen Kontext dem einzigen Dispatcher-Servlet zuzuordnen. Es sind keine übermäßig komplexen Lösungen erforderlich!

Die Parent-Child-Beziehung wird jedoch nützlich, wenn mehrere Dispatcher-Servlets konfiguriert sind. Aber wann sollten wir uns die Mühe machen, mehr als eine zu haben?

Im Allgemeinenwe declare multiple dispatcher servletswhen we need multiple sets of MVC configuration. Beispielsweise verfügen wir möglicherweise über eine REST-API neben einer herkömmlichen MVC-Anwendung oder einem ungesicherten und sicheren Abschnitt einer Website:

image

Hinweis: Wenn wirAbstractDispatcherServletInitializer erweitern (siehe Abschnitt 3.4), registrieren wir sowohl einen Root-Webanwendungskontext als auch ein einzelnes Dispatcher-Servlet.

Wenn wir also mehr als ein Servlet möchten, benötigen wir mehrereAbstractDispatcherServletInitializer-Implementierungen. Wir können jedoch nur einen Stammkontext definieren, oder die Anwendung wird nicht gestartet.

Glücklicherweise kann die MethodecreateRootApplicationContextnull zurückgeben. Daher können wir Implementierungen von einemAbstractContextLoaderInitializer und vielenAbstractDispatcherServletInitializer haben, die keinen Stammkontext erstellen. In einem solchen Szenario ist es ratsam, die Initialisierer explizit mit@Order zu bestellen.

Beachten Sie außerdem, dassAbstractDispatcherServletInitializer das Servlet unter einem bestimmten Namen (dispatcher) registriert und wir natürlich nicht mehrere Servlets mit demselben Namen haben können. Wir müssen alsogetServletName überschreiben:

@Override
protected String getServletName() {
    return "another-dispatcher";
}

5. Ein übergeordnetes und untergeordnetes Kontextbeispiel

Nehmen wir an, wir haben zwei Bereiche unserer Anwendung, zum Beispiel einen öffentlichen, der weltweit zugänglich ist, und einen gesicherten Bereich mit unterschiedlichen MVC-Konfigurationen. Hier definieren wir nur zwei Controller, die eine andere Nachricht ausgeben.

Angenommen, einige der Controller benötigen einen Dienst, der über erhebliche Ressourcen verfügt. Ein allgegenwärtiger Fall ist die Beharrlichkeit. Dann möchten wir diesen Service nur einmal instanziieren, um eine Verdoppelung der Ressourcennutzung zu vermeiden, und weil wir an das Prinzip "Nicht wiederholen" glauben!

Fahren wir nun mit dem Beispiel fort.

5.1. Der Shared Service

In unserem Beispiel "Hallo Welt" haben wir uns für einen einfacheren Begrüßungsservice entschieden, anstatt für Ausdauer:

package com.example.contexts.services;

@Service
public class GreeterService {
    @Resource
    private Greeting greeting;

    public String greet() {
        return greeting.getMessage();
    }
}

Wir deklarieren den Dienst im Kontext der Stammwebanwendung mithilfe des Komponentenscannings:

@Configuration
@ComponentScan(basePackages = { "com.example.contexts.services" })
public class RootApplicationConfig {
    //...
}

Möglicherweise bevorzugen wir stattdessen XML:

5.2. Die Controller

Definieren wir zwei einfache Controller, die den Dienst verwenden und eine Begrüßung ausgeben:

package com.example.contexts.normal;

@Controller
public class HelloWorldController {

    @Autowired
    private GreeterService greeterService;

    @RequestMapping(path = "/welcome")
    public ModelAndView helloWorld() {
        String message = "

Normal " + greeterService.greet() + "

"; return new ModelAndView("welcome", "message", message); } } //"Secure" Controller package com.example.contexts.secure; String message = "

Secure " + greeterService.greet() + "

";

Wie wir sehen können, liegen die Controller in zwei verschiedenen Paketen und drucken unterschiedliche Meldungen: Die eine sagt "normal", die andere "sicher".

5.3. Die Dispatcher-Servlet-Kontexte

Wie bereits erwähnt, werden wir zwei verschiedene Dispatcher-Servlet-Kontexte haben, einen für jeden Controller. Definieren wir sie also in Java:

//Normal context
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example.contexts.normal" })
public class NormalWebAppConfig implements WebMvcConfigurer {
    //...
}

//"Secure" context
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example.contexts.secure" })
public class SecureWebAppConfig implements WebMvcConfigurer {
    //...
}

Oder, wenn wir es vorziehen, in XML:





5.4. Alles zusammenfügen

Jetzt, wo wir alle Teile haben, müssen wir Spring nur noch sagen, dass er sie verkabeln soll. Denken Sie daran, dass wir den Stammkontext laden und die beiden Dispatcher-Servlets definieren müssen. Obwohl wir mehrere Möglichkeiten gesehen haben, konzentrieren wir uns jetzt auf zwei Szenarien, ein Java-Szenario und ein XML-Szenario. Let’s start with Java.

Wir definieren einAbstractContextLoaderInitializer, um den Stammkontext zu laden:

@Override
protected WebApplicationContext createRootApplicationContext() {
    AnnotationConfigWebApplicationContext rootContext
      = new AnnotationConfigWebApplicationContext();
    rootContext.register(RootApplicationConfig.class);
    return rootContext;
}

Dann müssen wir die beiden Servlets erstellen, damit wir zwei Unterklassen vonAbstractDispatcherServletInitializer definieren. Erstens die "normale":

@Override
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext normalWebAppContext
      = new AnnotationConfigWebApplicationContext();
    normalWebAppContext.register(NormalWebAppConfig.class);
    return normalWebAppContext;
}

@Override
protected String[] getServletMappings() {
    return new String[] { "/api/*" };
}

@Override
protected String getServletName() {
    return "normal-dispatcher";
}

Dann die "sichere", die einen anderen Kontext lädt und einem anderen Pfad zugeordnet ist:

@Override
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext secureWebAppContext
      = new AnnotationConfigWebApplicationContext();
    secureWebAppContext.register(SecureWebAppConfig.class);
    return secureWebAppContext;
}

@Override
protected String[] getServletMappings() {
    return new String[] { "/s/api/*" };
}

@Override
protected String getServletName() {
    return "secure-dispatcher";
}

Und wir sind fertig! Wir haben gerade das angewendet, was wir in den vorherigen Abschnitten berührt haben.

We can do the same with web.xml, wiederum nur durch Kombinieren der Teile, die wir bisher besprochen haben.

Definieren Sie einen Stammanwendungskontext:


    
        org.springframework.web.context.ContextLoaderListener
    

Ein "normaler" Dispatcher-Kontext:


    normal-webapp
    
        org.springframework.web.servlet.DispatcherServlet
    
    1


    normal-webapp
    /api/*

Und schließlich ein "sicherer" Kontext:


    secure-webapp
    
        org.springframework.web.servlet.DispatcherServlet
    
    1


    secure-webapp
    /s/api/*

6. Mehrere Kontexte kombinieren

Es gibt andere Möglichkeiten als Eltern-Kind, um mehrere Konfigurationsspeicherorte zu kombinieren.to split big contexts and better separate different concerns. Wir haben bereits ein Beispiel gesehen: Wenn wircontextConfigLocation mit mehreren Pfaden oder Paketen angeben, erstellt Spring einen einzelnen Kontext, indem alle kombiniert werden Bean-Definitionen, als wären sie der Reihe nach in eine einzelne XML-Datei oder Java-Klasse geschrieben.

Wir können jedoch mit anderen Mitteln einen ähnlichen Effekt erzielen und sogar unterschiedliche Ansätze gemeinsam verfolgen. Lassen Sie uns unsere Optionen untersuchen.

Eine Möglichkeit ist das Scannen von Komponenten, das wirin another article erklären.

6.1. Einen Kontext in einen anderen importieren

Alternativ kann eine Kontextdefinition eine andere importieren. Je nach Szenario haben wir verschiedene Arten von Importen.

Importieren einer@Configuration-Klasse in Java:

@Configuration
@Import(SomeOtherConfiguration.class)
public class Config { ... }

Laden eines anderen Ressourcentyps, beispielsweise einer XML-Kontextdefinition, in Java:

@Configuration
@ImportResource("classpath:basicConfigForPropertiesTwo.xml")
public class Config { ... }

Zum Schluss eine XML-Datei in eine andere einbinden:

So haben wir viele Möglichkeiten, die Dienste, Komponenten, Controller usw. zu organisieren, die zusammenarbeiten, um unsere großartige Anwendung zu erstellen. Und das Schöne ist, dass IDEs sie alle verstehen!

7. Spring Boot-Webanwendungen

Spring Boot automatically configures the components of the application, Im Allgemeinen muss weniger darüber nachgedacht werden, wie sie organisiert werden sollen.

Unter der Haube verwendet Boot jedoch Spring-Funktionen, einschließlich der Funktionen, die wir bisher gesehen haben. Sehen wir uns einige bemerkenswerte Unterschiede an.

Spring Boot-Webanwendungen, die in einem eingebetteten Containerdon’t run any WebApplicationInitializerausgeführt werden.

Sollte es notwendig sein, können wir stattdessen dieselbe Logik inSpringBootServletInitializer oder aServletContextInitializer schreiben, abhängig von der gewählten Bereitstellungsstrategie.

Zum Hinzufügen von Servlets, Filtern und Listenern, wie in diesem Artikel beschrieben, ist dies jedoch nicht erforderlich. In fact, Spring Boot automatically registers every servlet-related bean to the container:

@Bean
public Servlet myServlet() { ... }

Die so definierten Objekte werden gemäß Konventionen zugeordnet: Filter werden automatisch / * zugeordnet, dh jeder Anforderung. Wenn wir ein einzelnes Servlet registrieren, wird es / zugeordnet, andernfalls wird jedes Servlet seinem Bean-Namen zugeordnet.

Wenn die oben genannten Konventionen für uns nicht funktionieren, können wir stattdessenFilterRegistrationBean,ServletRegistrationBean, oderServletListenerRegistrationBean definieren. Mit diesen Kursen können wir die Feinheiten der Registrierung kontrollieren.

8. Schlussfolgerungen

In diesem Artikel erhalten Sie einen detaillierten Überblick über die verschiedenen Optionen zum Strukturieren und Organisieren einer Spring-Webanwendung.

Wir haben einige Funktionen ausgelassen, insbesondere diesupport for a shared context in enterprise applications, die zum Zeitpunkt des Schreibens nochmissing from Spring 5 sind.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inthe GitHub project - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und unverändert auszuführen sein.