为什么自定义的 HandlerMethodArgumentResolver 没有被执行呢?

本文将介绍怎样解决自定义的 HandlerMethodArgumentResolver 没有被执行呢?

背景

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

如果在胜利前却步,往往只会拥抱失败;如果在困难时坚持,常常会获得新的成功。
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,请来信告知:hxstrive@outlook.com
公众号