通常开发 HTTP 接口,总避免不了对参数进行详细的验证。为了便捷的验证参数,我决定使用自定义注解来验证 HTTP 接口的参数,我们选择使用 HandlerMethodArgumentResolver 来实现。遗憾的是我们自定义的 HandlerMethodArgumentResolver 并没有被执行,甚至重写 WebMvcConfigurer 的 addArgumentResolvers() 方法也没有被执行?这是为什么呢?
下面是我们自定义 HandlerMethodArgumentResolver 而写的代码,如下:
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.List; @Configuration public class Demo1Handel extends WebMvcConfigurerAdapter { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { // 没有执行,因此没有任何输出 System.out.println(this.getClass().getName() + " addArgumentResolvers()"); argumentResolvers.add(new HandlerMethodArgumentResolver(){ @Override public boolean supportsParameter(MethodParameter methodParameter) { // Demo1Annotation 为自定义的注解,在接口参数前面被使用 return methodParameter.hasParameterAnnotation(Demo1Annotation.class); } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { // 获取修饰的参数名称 String paramName = methodParameter.getParameterName(); // 获取被修饰参数接收的值 String paramValue = nativeWebRequest.getParameter(paramName); if(methodParameter.hasParameterAnnotation(Demo1Annotation.class)) { System.out.println("Demo1Annotation"); System.out.println(paramName + " ==> " + paramValue); } return paramValue; } }); } }
在控制器的接口中去使用自定义注解,实现参数验证,如下:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/demo1") public class Demo1Controller { @GetMapping("/demo") public String demo(@Demo1Annotation String msg, String version){ return "From Server ==> msg=" + msg + ", version=" + version; } }
在 IDEA 中,展开 “External Libraries” 依赖,找到 spring-boot-autoconfigure-2.3.7.RELEASE.jar 依赖,打开 “META-INF/spring.factories” 文件。在该文件中配置了自动配置类,如下:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ ... org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ ... 打开 WebMvcAutoConfiguration 类,找到 EnableWebMvcConfiguration 内部类,代码如下: /** * Configuration equivalent to {@code @EnableWebMvc}. */ @Configuration(proxyBeanMethods = false) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { //... @Bean @Override public RequestMappingHandlerAdapter requestMappingHandlerAdapter( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcValidator") Validator validator) { // 调用 super.requestMappingHandlerAdapter() 方法去初始化 RequestMappingHandlerAdapter RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager, conversionService, validator); adapter.setIgnoreDefaultModelOnRedirect( this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect()); return adapter; } //... }
super.requestMappingHandlerAdapter() 代码如下:
@Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcValidator") Validator validator) { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); adapter.setContentNegotiationManager(contentNegotiationManager); adapter.setMessageConverters(getMessageConverters()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator)); // 获取我们自定义的参数解析器 adapter.setCustomArgumentResolvers(getArgumentResolvers()); //... return adapter; }
getArgumentResolvers() 方法代码如下:
protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() { if (this.argumentResolvers == null) { this.argumentResolvers = new ArrayList<>(); // 这不就是调用我们重写的那个方法吗? addArgumentResolvers(this.argumentResolvers); } return this.argumentResolvers; } // 添加自定义 HandlerMethodArgumentResolver protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { }
上面介绍了自定义 HandlerMethodArgumentResolver 被添加到 RequestMappingHandlerAdapter 的过程。如果你仔细看 WebMvcAutoConfiguration 类的声明,就会知道:
// 它是一个配置类 @Configuration(proxyBeanMethods = false) // 只有基于servlet的web应用程序才会匹配,即应用是一个 WEB 应用程序 @ConditionalOnWebApplication(type = Type.SERVLET) // 在 classpath 中需要存在 Servlet、DispatcherServlet 和 WebMvcConfigurer @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) // 在 Spring 容器中不能有 WebMvcConfigurationSupport 的实例 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) // 指定自动配置顺序 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) // 指定它需要在 DispatcherServletAutoConfiguration、TaskExecutionAutoConfiguration 和 ValidationAutoConfiguration 之后才能自动配置 @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { //... }
如果要实现参数解析,我们可以通过下面几种方式:
(1)继承 WebMvcConfigurerAdapter 类
(2)自己实现 WebMvcConfigurer 接口
(3)继承 WebMvcConfigurationSupport 类
我上面应用程序自定义的参数解析器没有生效,是因为在我们的 Spring 容器中已经存在 WebMvcConfigurationSupport 的实例,导致 WebMvcAutoConfiguration 没有匹配,也就不会进行自动配置导致的。
如果要解决这个问题,需要将所有自定义参数解析器改为 WebMvcConfigurationSupport 去实现。代码如下:
@Configuration public class Demo1Handel extends WebMvcConfigurationSupport { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { System.out.println(this.getClass().getName() + " addArgumentResolvers()"); argumentResolvers.add(new HandlerMethodArgumentResolver(){ @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.hasParameterAnnotation(Demo5Annotation.class); } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { // 获取修饰的参数名称 String paramName = methodParameter.getParameterName(); // 获取被修饰参数接收的值 String paramValue = nativeWebRequest.getParameter(paramName); if(methodParameter.hasParameterAnnotation(Demo1Annotation.class)) { System.out.println("Demo1Annotation"); System.out.println(paramName + " ==> " + paramValue); } return paramValue; } }); } }
上面是笔者自己遇到问题时的处理方法,问题也被成功处理。但是,如果你遇到该问题,不一定能够成功解决……
在一个 Spring Boot 项目中只允许声明一个 WebMvcConfigurationSupport 配置类,这是因为 WebMvcAutoConfiguration 类上面存在 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 注解;
在一个 Spring Boot 项目中允许声明多个 WebMvcConfigurer 接口或 WebMvcConfigurerAdapter 抽象类。但是,WebMvcConfigurerAdapter 类已经不建议使用了;