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