SpringMVC源码(3) - HandlerMethodArgumentResolver

1.前言

在上一篇文章中,我们留下了好几个点没有分析,其中有一个就是HandlerMethodArgumentResolver类,这个类呢,就是用于处理方法的参数解析。用于比如@RequestBody,@RequestParam以及我们自定义等的参数处理。

2.例子

我们这里有一个例子,根据请求头是否有jiang来鉴权,没有就抛出异常。这里例子比较简单,如果真正业务场景的话,可能就会做更多事情。

1.注解

就是一个简单的@CurrentUser

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentUser {
}

2.解析器定义

参数解析器有两个方法:
1.supportsParameter:用于判断是否该解析器支持解析
2.resolveArgument:当解析器支持的话,就会调用该方法解析参数对象
这里的解析就是判断请求头有没有带有name是jiang的参数,有就返回

public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        String name = request.getHeader("name");
        if (!"jiang".equals(name)) {
            throw new RuntimeException("鉴权错误");
        }

        return name;
    }
}

3.配置解析器

下面的代码也比较简单,主要是注入了一个WebMvcConfigurer的Bean,这个Bean重写了addArgumentResolvers,将我们的CurrentUserArgumentResolver参数解析器加入了。

@Slf4j
@Configuration
public class BootWebConfiguration {

    @Bean
    WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
                resolvers.add(new CurrentUserArgumentResolver());
            }
        };
    }
}

3.参数解析器加载流程

上面我们看到了只要注入一个WebMvcConfigurer,然后里面实现方法即可让这个参数解析器生效。那这个解析器又是如何被使用生效的呢,下面开始分析

1.SpringBoot对SpringMVC的加载

众所周知springboot的对各种组件,类似redis,kafka等都是用starter的模式,通过spring.factories文件来加载。
SpringMVC中也不例外。
org.springframework.boot:spring-boot-autoconfigure包中的spring.factories

# .......
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
# ......

上面有很多AutoConfiguration,其中包括DispatcherServlet的,以及ServletWebServerFactory用于加载tomcat,还是jetty容器等等。
其中WebMvcAutoConfiguration就是我们启动的主角了。

2.WebMvcAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
  // *****
}

先看定义,这有其中有一个要求是@ConditionalOnMissingBean(WebMvcConfigurationSupport.class),也就是如果我们自定义了WebMvcConfigurationSupport类型的Bean,那这个WebMvcAutoConfiguration就不会生效了。
所以这里也涉及到了一个常见的错误,很多人要加请求拦截器,或者啥的都继承WebMvcConfigurationSupport,然后重写里面的方法,这样写是不太规范的,最好是像我们上面的一样,注入WebMvcConfigurer来实现。

再继续看里面的代码,会发现有个内部的Bean

@Configuration(proxyBeanMethods = false)
    @EnableConfigurationProperties(WebProperties.class)
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
              
        // 注入RequestMappingHandlerAdapter 
        @Bean
        @Override
        public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
                @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
                @Qualifier("mvcConversionService") FormattingConversionService conversionService,
                @Qualifier("mvcValidator") Validator validator) {
            RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
                    conversionService, validator);
            adapter.setIgnoreDefaultModelOnRedirect(
                    this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
            return adapter;
        }
      
        // 注入RequestMappingHandlerMapping 
        @Bean
        @Primary
        @Override
        public RequestMappingHandlerMapping requestMappingHandlerMapping(
                @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
                @Qualifier("mvcConversionService") FormattingConversionService conversionService,
                @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
            // Must be @Primary for MvcUriComponentsBuilder to work
            return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
                    resourceUrlProvider);
        }

这里会注入RequestMappingHandlerAdapter,以及RequestMappingHandlerMapping。根据上两篇文章分析,这两个Bean,就是用于我们@RequestMapping的这种形式的处理hander,以及handlerAdapter。

我们既然是分析对参数的解析,那自然是要看RequestMappingHandlerAdapter这个在请求流程中至关重要Bean的注入了。
我们看到它调用了super.requestMappingHandlerAdapter(contentNegotiationManager, conversionService, validator);

我们先看一下EnableWebMvcConfiguration它的类继承关系

image.png

这里可以看到DelegatingWebMvcConfiguration这个类其实是个中间层的代理类。它有个函数setConfigurers,这里会将所有的WebMvcConfigurer都注入进来,然后加入到WebMvcConfigurerComposite
然后其他的方法,就是挨个调用WebMvcConfigurerCompositeWebMvcConfigurer来处理。

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// WebMvcConfigurer的组合类
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

@Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        this.configurers.addInterceptors(registry);
    }

    // ******

接着往上看它的父类WebMvcConfigurationSupport,它的东西就是核心了。

3.WebMvcConfigurationSupport

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    // 创建RequestMappingHandlerMapping
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping(
            @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
            @Qualifier("mvcConversionService") FormattingConversionService conversionService,
            @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

        RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
        mapping.setOrder(0);
        mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
        mapping.setContentNegotiationManager(contentNegotiationManager);
        mapping.setCorsConfigurations(getCorsConfigurations());
                // ...
        return mapping;
    }

@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());
        adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

        return adapter;
    }
        
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    }
    protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
    }
}

上面我们可以看到,它实现了创建RequestMappingHandlerMapping, RequestMappingHandlerAdapter,同时还留了addArgumentResolvers,addReturnValueHandlers这种接口给子类去实现。

那我们先研究RequestMappingHandlerAdapter,因为这里可以看到有一句,adapter.setCustomArgumentResolvers(getArgumentResolvers()); 用于设置参数解析器。
同时这里也是有adapter.setCustomReturnValueHandlers(getReturnValueHandlers());用于设置返回值处理器。
这里我们先看参数解析器的逻辑,其实和设置返回值处理器的逻辑是一致的。

发现会调用getArgumentResolvers

    protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {
        if (this.argumentResolvers == null) {
            this.argumentResolvers = new ArrayList<>();
            addArgumentResolvers(this.argumentResolvers);
        }
        return this.argumentResolvers;
    }

第一次的话这个argumentResolvers肯定是空的,所以会调用addArgumentResolvers,注意,这里是传了一个List进去,意味着如果要增加参数解析器只需要向这里面添加就好了,而这个刚好是由DelegatingWebMvcConfiguration实现了。

    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        this.configurers.addArgumentResolvers(argumentResolvers);
    }

在看configurers的addArgumentResolvers

lass WebMvcConfigurerComposite implements WebMvcConfigurer {
    // Web配置类列表
    private final List<WebMvcConfigurer> delegates = new ArrayList<>();

    public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.delegates.addAll(configurers);
        }
    }

    // 遍历WebMvcConfigurer,调用addArgumentResolvers
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addArgumentResolvers(argumentResolvers);
        }
    }
  
    // 遍历WebMvcConfigurer,调用addReturnValueHandlers
    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addReturnValueHandlers(returnValueHandlers);
        }
}

这个XXXComposite模式是不是有点似曾相似,没错,前面HandlerMethodArgumentResolverComposite啥的,都是和这个一个套路,将所有的WebMvcConfigurer配置类都组合到一起,做增强处理,同时自己也继承了WebMvcConfigurer
而这个configurersDelegatingWebMvcConfiguration.setConfigurers就将Spring中的所有WebMvcConfigurer都注入了。

所以,当调用WebMvcConfigurationSupport.getArgumentResolvers的时候,就会调用到DelegatingWebMvcConfigurationaddArgumentResolvers,然后调用到我们自己的代码:

    @Bean
    WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(handlerInterceptor());
            }

            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
                resolvers.add(new CurrentUserArgumentResolver());
            }
        };
    }

最终将这个参数解析器放入到WebMvcConfigurationSupportargumentResolvers中。

那么到这里,我们已经将我们自己定义的参数解析器放入到了处理器适配器RequestMappingHandlerAdapter的customArgumentResolvers中了

4.RequestMappingHandlerAdapter中的参数解析器

上面的第三步,我们已经将CurrentUserArgumentResolver这个自定义的参数解析器放到了RequestMappingHandlerAdaptercustomArgumentResolvers中了。接下来,这个RequestMappingHandlerAdapter就要初始化了。

可以看到RequestMappingHandlerAdapter是实现InitializingBean,所以会进入afterPropertiesSet方法

@Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
        initControllerAdviceCache();
         // 参数解析器
        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }

        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }

        // 返回值处理器
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

这里有getDefaultArgumentResolvers(),然后获取出来了以后,创建一个HandlerMethodArgumentResolverComposite,赋值给argumentResolvers
可以看下这个getDefaultArgumentResolvers的代码:

    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

        // @RequestParam的解析
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        // @PathVariable的解析
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        // @RequestBody的解析
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        // .....

        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }

        // Catch-all
        resolvers.add(new PrincipalMethodArgumentResolver());
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));

        return resolvers;
    }

这里可以看到其实默认的参数解析器是有很多的,比如处理@RequestParam的,@PathVariable的,以及@RequestBody的,我们的自定义会在getCustomArgumentResolvers()取出,然后加入进去。返回值参数处理器,也是类似的。
所以这里也有小的知识点:就是我们自己定义的参数解析器顺序是比较靠后的,在参数解析的时候,都是优先遍历放在前面的参数解析器。

最终我们将所有的参数解析器都放在了RequestMappingHandlerAdapter的argumentResolvers中了。
至此,我们的初始化就以及完成了,后面就是请求进来后,找解析器的工作了。

5.使用参数解析器

当前面初始化好了,也可以接受请求的时候,这个时候请求进来了。

根据之前的流程分析,这个请求会从DispatcherSerlvet有的doDispatch一直调用,然后到RequestMappingHandlerAdapterinvokeHandlerMethod

@Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
          // 设置参数解析器到`ServletInvocableHandlerMethod `
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            // 设置返回值处理器到`ServletInvocableHandlerMethod `
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
            // 调用
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
    }

上面的代码可以看到这个参数解析器被设置到了ServletInvocableHandlerMethod中,这个类我们之前也分析了。
它就是一个可以对这次请求进行参数解析,调用处理方法,最后对返回值解析的类。

然后我们一路跟进到invokeForRequest,再到getMethodArgumentValues

    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
    

        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }

            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        return args;
    }

在这它用resolvers变量,这个变量就是之前的RequestMappingHandlerAdapter的argumentResolvers,然后他就调用每个参数解析器的supportsParameter,判断是否支持,支持了就调用resolveArgument来获得这个解析后的参数。

所以到这里,我们就分析完成了HandlerMethodArgumentResolver是如何加载,以及如何被使用的。

6.总结

1.SpringBoot加载WebMvcAutoConfiguration

2.调用EnableWebMvcConfiguration,然后调用父类的DelegatingWebMvcConfiguration,将自定义配置WebMvcConfigurer存放到WebMvcConfigurerComposite

3.调用requestMappingHandlerAdapter,然后调用到顶级父类WebMvcConfigurationSupport的requestMappingHandlerAdapter来创建这个RequestMappingHandlerAdapter

4.调用RequestMappingHandlerAdapter的getArgumentResolvers,然后会调用DelegatingWebMvcConfiguration里面的WebMvcConfigurerComposite,最终回调自定义的WebMvcConfigurer,将HandlerMethodArgumentResolver参数解析器加入到RequestMappingHandlerAdapter中

5.请求进来后,从DispatcherSerlvet一直调用到RequestMappingHandlerAdapter的invokeHandlerMethod,
然后创建了一个ServletInvocableHandlerMethod,并且将WebMvcConfigurerComposite放到它的里面

6.到ServletInvocableHandlerMethod的getMethodArgumentValues里,将处理方法的所以参数都取出来,循环遍历判断参数解析器是否支持,支持的话就解析

7.后续

前面可以看到了这个HandlerMethodArgumentResolver是有非常多的种类的,后续我们会对这些种类进行一些分析,特别是RequestResponseBodyMethodProcessor,这个是比较复杂的,它不仅把参数解析器实现了,还把返回值处理器也给实现了,内部用HttpMessageConverter,实现了多种数据转换器,其中内置包含了String,gson,Jackson等等的。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容