通常开发 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 类已经不建议使用了;