自定义Spring MVC控制器的映射

引言


最近,我面临着实现一个控制器的任务,该控制器以各种方式处理有请求参数和无请求参数的情况。 由于控制器中确实需要两种不同的方法,使得问题更加复杂。 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 //     public String handleRequestWithoutParams() {        return HANDLE_REQUEST_WITHOUT_PARAMS;    } } 

让我们开始吧


@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示例代码

Source: https://habr.com/ru/post/zh-CN478766/


All Articles