Passen Sie die Zuordnung von Spring MVC-Controllern an

Einleitung


Vor kurzem stand ich vor der Aufgabe, einen Controller zu implementieren, der auf verschiedene Arten mit Situationen umgeht, in denen es Anforderungsparameter gibt und in denen es keine gibt. Das Problem wurde durch die Tatsache verschärft, dass genau zwei verschiedene Methoden in der Steuerung benötigt wurden. Die Standardfunktionen von Spring MVC erlaubten dies nicht. Ich musste etwas tiefer graben. Wen interessiert das - willkommen bei cat.


Was wollen wir?


Hier ist ein kurzer Test, der das Wesentliche der Aufgabe beschreibt.


TestControllerTest.java
@SpringJUnitWebConfig(WebConfig.class) class TestControllerTest {     @Autowired     WebApplicationContext webApplicationContext;     @Test     void testHandleRequestWithoutParams() throws Exception {         MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();         mockMvc.perform(MockMvcRequestBuilders.get("/test"))                 .andExpect(status().isOk())                 .andExpect(result ->                         assertEquals(TestController.HANDLE_REQUEST_WITHOUT_PARAMS, result.getResponse().getContentAsString()));     }     @Test     void testHandleRequestWithParams() throws Exception {         MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();         mockMvc.perform(MockMvcRequestBuilders                 .get("/test")                 .param("someparam", "somevalue"))                 .andExpect(status().isOk())                 .andExpect(result ->                         assertEquals(TestController.HANDLE_REQUEST_WITH_PARAMS, result.getResponse().getContentAsString()));     } } 

Wir hoffen, dass alles von selbst gelöst wird


TestController.java
 @Controller @RequestMapping("/test") public class TestController {    public static final String HANDLE_REQUEST_WITH_PARAMS = "handleRequestWithParams";    public static final String HANDLE_REQUEST_WITHOUT_PARAMS = "handleRequestWithoutParams";    @GetMapping    @ResponseBody    public String handleRequestWithParams(SearchQuery query) {        return HANDLE_REQUEST_WITH_PARAMS;    }    @GetMapping    @ResponseBody    public String handleRequestWithoutParams() {        return HANDLE_REQUEST_WITHOUT_PARAMS;    } } 

Der Frühling war jedoch nicht so freundlich wie erwartet, und die Ausgabe war:


 java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'getTestController' method public java.lang.String ru.pchurzin.spring.customannotations.TestController.handleRequestWithoutParams() to {GET /test}: There is already 'getTestController' bean method public java.lang.String ru.pchurzin.spring.customannotations.TestController.handleRequestWithParams(ru.pchurzin.spring.customannotations.SearchQuery) mapped.` 

Gib nicht auf


Es wurde versucht, die Zuordnung dieser Methoden über die @RequestMapping(params = "some condition") zu trennen. Leider ist es in der verwendeten Version von Spring 5.1.8 nicht möglich, die Bedingung für die Anforderung so festzulegen, dass sie Parameter enthält. Sie können entweder das Vorhandensein von Parametern mit bestimmten Namen oder das Fehlen von Parametern mit bestimmten Namen angeben. Weder der eine noch der andere war geeignet, weil Die Parameter können bei jeder Anfrage unterschiedlich sein. Ich möchte so etwas @RequestMapping(params = "*") schreiben, um anzugeben, dass die Anfrage einige Parameter enthalten soll, oder @RequestMapping (params = "! *"), Um anzugeben, was nicht in der Anfrage enthalten sein soll keine Parameter sein.


Und was steht in der Dokumentation?


Nach dem Rauchen der Dokumentation wurde der Abschnitt CustomAnnotations gefunden, in dem wir sehen:


 Spring MVC also supports custom request-mapping attributes with custom request-matching logic. 

Es wurde beschlossen, eine Anmerkung zu machen, mit der ich die Bedingungen angeben konnte, die ich für die Verfügbarkeit von Abfrageparametern benötigte.


Zweck


Ich möchte der Methode die Annotation @NoRequestParams hinzufügen, die @NoRequestParams , dass diese Methode Anforderungen ohne Parameter verarbeitet.


 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRequestParams { } 

Controller mit unserer Anmerkung:


Testcontoller.java
 @Controller @RequestMapping("/test") public class TestController {    public static final String HANDLE_REQUEST_WITH_PARAMS = "handleRequestWithParams";    public static final String HANDLE_REQUEST_WITHOUT_PARAMS = "handleRequestWithoutParams";    @GetMapping    @ResponseBody    public String handleRequestWithParams(SearchQuery query) {        return HANDLE_REQUEST_WITH_PARAMS;    }    @GetMapping    @ResponseBody @NoRequestParams //     public String handleRequestWithoutParams() {        return HANDLE_REQUEST_WITHOUT_PARAMS;    } } 

Fangen wir an


Die Standard-Spring-MVC-Konfiguration, die durch die Anmerkung @EnableWebMvc aktiviert wird, wird in der Klasse WebMvcConfigurationSupport beschrieben . Es instanziiert ein Bean der RequestMappingHandlerMapping- Klasse, das die Zuordnung mithilfe von Objekten der RequestMappingInfo- Klasse implementiert , die wiederum Informationen zur Zuordnung von Methodenanforderungen kapseln. Diese Informationen werden in Form von Bedingungsklassenobjekten dargestellt, die die RequestCondition- Schnittstelle implementieren . Spring hat 6 fertige Implementierungen:



Zusätzlich zu diesen Implementierungen können wir unsere eigenen definieren. Dazu müssen wir die RequestCondition- Schnittstelle implementieren und diese Implementierung für unsere Anforderungen verwenden. Sie können die Schnittstelle direkt implementieren oder eine abstrakte Klasse verwenden
AbstractRequestCondition .


NoRequestParamsCondition.java
 public class NoRequestParamsCondition extends AbstractRequestCondition<NoRequestParamsCondition> {    public final static NoRequestParamsCondition NO_PARAMS_CONDITION = new NoRequestParamsCondition();    @Override    protected Collection<?> getContent() {        return Collections.singleton("no params");    }    @Override    protected String getToStringInfix() {        return "";    }    @Override    public NoRequestParamsCondition combine(NoRequestParamsCondition other) {        return this;    }    @Override    public NoRequestParamsCondition getMatchingCondition(HttpServletRequest request) {        if (request.getParameterMap().isEmpty()) {            return this;        }        return null;    }    @Override    public int compareTo(NoRequestParamsCondition other, HttpServletRequest request) {        return 0;    } } 

Die ersten beiden Methoden werden verwendet, um unsere Bedingung als Zeichenfolge darzustellen.


Die T-Combine- Methode (T other) wird benötigt, um Bedingungen zu kombinieren, z. B. wenn Anmerkungen zu Methode und Klasse vorhanden sind. In diesem Fall wird die Kombinationsmethode zum Kombinieren verwendet. Unsere Anmerkung impliziert keine Kombination - daher geben wir einfach unsere aktuelle Instanz der Bedingung zurück.


Die Methode int compareTo (T other, HttpServletRequest request) wird zum Vergleichen von Bedingungen im Kontext einer bestimmten Anforderung verwendet. Das heißt Wenn mehrere Bedingungen desselben Typs für die Anforderung vorliegen, wird ermittelt, welche am spezifischsten ist. Aber auch hier ist unsere Bedingung die einzig mögliche, daher geben wir einfach 0 zurück, d. H. Alle unsere Bedingungen sind gleich.


Die grundlegende Logik der Arbeit ist in der T- Methode getMatchingCondition (HttpServletRequest-Anforderung) enthalten . Bei dieser Methode müssen wir uns entscheiden, ob unsere Bedingung darauf zutrifft oder nicht. Wenn ja, geben Sie das Bedingungsobjekt zurück. Wenn nicht, geben Sie null . In unserem Fall geben wir ein Bedingungsobjekt zurück, wenn die Anforderung keine Parameter enthält.


Jetzt müssen wir unsere Bedingung in den Mapping-Prozess einbeziehen. Zu diesem Zweck übernehmen wir die Standardimplementierung von RequestMappingHandlerMapping
und definieren Sie die getCustomMethodCondition(Method method) , die soeben erstellt wurde, um Ihre eigenen benutzerdefinierten Bedingungen hinzuzufügen. Darüber hinaus werden mit dieser Methode die Bedingungen für Controller- Methoden ermittelt . Es gibt auch eine getCustomTypeCondition(Class<?> handlerType) , mit der Bedingungen basierend auf Informationen zur Controller-Klasse ermittelt werden können. In unserem Fall brauchen wir es nicht.


Als Ergebnis haben wir die folgende Implementierung:


CustomRequestMappingHandlerMapping.java
 public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {    @Override    protected RequestCondition<?> getCustomMethodCondition(Method method) {        return method.isAnnotationPresent(NoRequestParams.class) ? NO_PARAMS_CONDITION : null;    } } 

Die Logik ist nicht kompliziert - wir prüfen das Vorhandensein unserer Anmerkung und geben, falls vorhanden, den Gegenstand unseres Zustands zurück.


Um unsere Mapping-Implementierung zu ermöglichen, erweitern wir die Standard-Spring-MVC-Konfiguration:


Webconfig.java
 @Configuration public class WebConfig extends WebMvcConfigurationSupport {       @Override    protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {        return new CustomRequestMappingHandlerMapping();    }    @Bean    public TestController getTestController() {        return new TestController();    } } 

Fügen Sie dem Controller unsere Anmerkung hinzu:


TestController.java
 @Controller @RequestMapping("/test") public class TestController {    public static final String HANDLE_REQUEST_WITH_PARAMS = "handleRequestWithParams";    public static final String HANDLE_REQUEST_WITHOUT_PARAMS = "handleRequestWithoutParams";    @GetMapping    @ResponseBody    public String handleRequestWithParams(SearchQuery query) {        return HANDLE_REQUEST_WITH_PARAMS;    }    @GetMapping    @ResponseBody    @NoRequestParams    public String handleRequestWithoutParams() {        return HANDLE_REQUEST_WITHOUT_PARAMS;    } } 

und überprüfe das Ergebnis:


 Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.851 s - in ru.pchurzin.spring.customannotations.TestControllerTest Results: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 

Auf diese Weise können wir die Logik des Mappings gemäß unseren Bedürfnissen ändern und unsere eigenen Bedingungen festlegen. Zum Beispiel Bedingungen zur IP-Adresse oder zum verwendeten User-Agent. Es mag einfachere Lösungen geben, aber in jedem Fall ist die Montage Ihres Fahrrads manchmal auch nützlich.


Vielen Dank für Ihre Aufmerksamkeit.


Github Beispielcode

Source: https://habr.com/ru/post/de478766/


All Articles