引言
最近,我面临着实现一个控制器的任务,该控制器以各种方式处理有请求参数和无请求参数的情况。 由于控制器中确实需要两种不同的方法,使得问题更加复杂。 Spring MVC的标准功能不允许这样做。 我不得不更深入地挖掘。 谁在乎-欢迎猫。
我们想要什么
这是描述任务本质的简短测试。
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())); } }
我们希望一切都会自己解决
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; } }
但是,Spring并不像预期的那样友好,输出为:
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.`
不要放弃
已经尝试通过@RequestMapping(params = "some condition")
注释来分离这些方法的映射,但是不幸的是,在使用的Spring 5.1.8版本中,无法设置请求条件以包含任何参数。 您可以指定使用特定名称的参数,也可以指定不使用特定名称的参数。 两者都不适合,因为 每个请求中的参数可能不同。 我想写@RequestMapping(params = "*")
来指示请求应包含一些参数,或者@RequestMapping(params =“!*”)`来指示请求中不应包含的内容没有参数。
文件中有什么?
抽掉文档后,发现CustomAnnotations部分,在其中可以看到:
Spring MVC also supports custom request-mapping attributes with custom request-matching logic.
决定进行注释,这使我能够指定查询参数的可用性所需的条件。
目的
我想将@NoRequestParams
批注添加到方法中,以指示该方法处理不带参数的请求。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRequestParams { }
带有我们注释的控制器:
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
让我们开始吧
由@EnableWebMvc注释激活的标准Spring-MVC配置在WebMvcConfigurationSupport类中进行了描述。 它实例化RequestMappingHandlerMapping类的bean,该类使用RequestMappingInfo类的对象实现映射,该对象又封装了有关方法请求映射的信息。 此信息以实现RequestCondition接口的条件类对象的形式表示。 Spring有6种现成的实现:
除了这些实现之外,我们还可以定义自己的实现。 为此,我们需要实现RequestCondition接口,并根据需要使用此实现。 您可以直接实现接口或使用抽象类
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; } }
前两个方法用于字符串表示我们的条件。
需要T组合(其他)方法来组合条件,例如,如果方法和类上有注释。 在这种情况下,使用合并方法进行合并。 我们的注释并不意味着组合-因此,我们仅返回条件的当前实例。
int compareTo(其他方法,HttpServletRequest请求)方法用于比较特定请求上下文中的条件。 即 如果请求有多个相同类型的条件,则会找出最具体的条件。 但是同样,我们的条件是唯一可能的条件,因此我们只返回0,即 我们所有的条件都是平等的。
工作的基本逻辑包含在T方法getMatchingCondition(HttpServletRequest请求)中 。 在这种方法中,我们必须决定查询我们的条件是否适用于它。 如果是这样,则返回条件对象。 如果不是,则返回null
。 在我们的例子中,如果请求不包含任何参数,我们将返回条件对象。
现在我们需要在映射过程中包括我们的条件。 为此,我们将从RequestMappingHandlerMapping的标准实现中继承
并重新定义getCustomMethodCondition(Method method)
,该方法只是为了添加您自己的自定义条件而创建的。 此外,该方法用于确定控制器方法的条件。 还有一个getCustomTypeCondition(Class<?> handlerType)
,该方法可用于基于有关控制器类的信息来确定条件。 就我们而言,我们不需要它。
结果,我们实现了以下实现:
CustomRequestMappingHandlerMapping.java public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping { @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { return method.isAnnotationPresent(NoRequestParams.class) ? NO_PARAMS_CONDITION : null; } }
逻辑并不复杂-我们检查注释的存在,如果存在,则返回条件对象。
为了启用我们的映射实施,我们将扩展标准的Spring MVC配置:
Webconfig.java @Configuration public class WebConfig extends WebMvcConfigurationSupport { @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { return new CustomRequestMappingHandlerMapping(); } @Bean public TestController getTestController() { return new TestController(); } }
将我们的注释添加到控制器:
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; } }
并检查结果:
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
因此,我们可以根据需要更改映射逻辑,并提出自己的条件。 例如,有关IP地址或使用的用户代理的条件。 可能有更简单的解决方案,但是在任何情况下,组装自行车有时也很有用。
谢谢您的关注。
Github示例代码