SpringMVC源碼(3) - HandlerMethodArgumentResolver

1.前言

在上一篇文章中,我們留下了好幾個(gè)點(diǎn)沒(méi)有分析,其中有一個(gè)就是HandlerMethodArgumentResolver類,這個(gè)類呢,就是用于處理方法的參數(shù)解析。用于比如@RequestBody,@RequestParam以及我們自定義等的參數(shù)處理。

2.例子

我們這里有一個(gè)例子,根據(jù)請(qǐng)求頭是否有jiang來(lái)鑒權(quán),沒(méi)有就拋出異常。這里例子比較簡(jiǎn)單,如果真正業(yè)務(wù)場(chǎng)景的話,可能就會(huì)做更多事情。

1.注解

就是一個(gè)簡(jiǎn)單的@CurrentUser

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

2.解析器定義

參數(shù)解析器有兩個(gè)方法:
1.supportsParameter:用于判斷是否該解析器支持解析
2.resolveArgument:當(dāng)解析器支持的話,就會(huì)調(diào)用該方法解析參數(shù)對(duì)象
這里的解析就是判斷請(qǐng)求頭有沒(méi)有帶有name是jiang的參數(shù),有就返回

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("鑒權(quán)錯(cuò)誤");
        }

        return name;
    }
}

3.配置解析器

下面的代碼也比較簡(jiǎn)單,主要是注入了一個(gè)WebMvcConfigurer的Bean,這個(gè)Bean重寫了addArgumentResolvers,將我們的CurrentUserArgumentResolver參數(shù)解析器加入了。

@Slf4j
@Configuration
public class BootWebConfiguration {

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

3.參數(shù)解析器加載流程

上面我們看到了只要注入一個(gè)WebMvcConfigurer,然后里面實(shí)現(xiàn)方法即可讓這個(gè)參數(shù)解析器生效。那這個(gè)解析器又是如何被使用生效的呢,下面開始分析

1.SpringBoot對(duì)SpringMVC的加載

眾所周知springboot的對(duì)各種組件,類似redis,kafka等都是用starter的模式,通過(guò)spring.factories文件來(lái)加載。
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就是我們啟動(dòng)的主角了。

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 {
  // *****
}

先看定義,這有其中有一個(gè)要求是@ConditionalOnMissingBean(WebMvcConfigurationSupport.class),也就是如果我們自定義了WebMvcConfigurationSupport類型的Bean,那這個(gè)WebMvcAutoConfiguration就不會(huì)生效了。
所以這里也涉及到了一個(gè)常見的錯(cuò)誤,很多人要加請(qǐng)求攔截器,或者啥的都繼承WebMvcConfigurationSupport,然后重寫里面的方法,這樣寫是不太規(guī)范的,最好是像我們上面的一樣,注入WebMvcConfigurer來(lái)實(shí)現(xiàn)。

再繼續(xù)看里面的代碼,會(huì)發(fā)現(xiàn)有個(gè)內(nèi)部的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);
        }

這里會(huì)注入RequestMappingHandlerAdapter,以及RequestMappingHandlerMapping。根據(jù)上兩篇文章分析,這兩個(gè)Bean,就是用于我們@RequestMapping的這種形式的處理hander,以及handlerAdapter。

我們既然是分析對(duì)參數(shù)的解析,那自然是要看RequestMappingHandlerAdapter這個(gè)在請(qǐng)求流程中至關(guān)重要Bean的注入了。
我們看到它調(diào)用了super.requestMappingHandlerAdapter(contentNegotiationManager, conversionService, validator);

我們先看一下EnableWebMvcConfiguration它的類繼承關(guān)系

image.png

這里可以看到DelegatingWebMvcConfiguration這個(gè)類其實(shí)是個(gè)中間層的代理類。它有個(gè)函數(shù)setConfigurers,這里會(huì)將所有的WebMvcConfigurer都注入進(jìn)來(lái),然后加入到WebMvcConfigurerComposite。
然后其他的方法,就是挨個(gè)調(diào)用WebMvcConfigurerCompositeWebMvcConfigurer來(lái)處理。

@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 {
    // 創(chuàng)建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) {
    }
}

上面我們可以看到,它實(shí)現(xiàn)了創(chuàng)建RequestMappingHandlerMapping, RequestMappingHandlerAdapter,同時(shí)還留了addArgumentResolvers,addReturnValueHandlers這種接口給子類去實(shí)現(xiàn)。

那我們先研究RequestMappingHandlerAdapter,因?yàn)檫@里可以看到有一句,adapter.setCustomArgumentResolvers(getArgumentResolvers()); 用于設(shè)置參數(shù)解析器。
同時(shí)這里也是有adapter.setCustomReturnValueHandlers(getReturnValueHandlers());用于設(shè)置返回值處理器。
這里我們先看參數(shù)解析器的邏輯,其實(shí)和設(shè)置返回值處理器的邏輯是一致的。

發(fā)現(xiàn)會(huì)調(diào)用getArgumentResolvers

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

第一次的話這個(gè)argumentResolvers肯定是空的,所以會(huì)調(diào)用addArgumentResolvers,注意,這里是傳了一個(gè)List進(jìn)去,意味著如果要增加參數(shù)解析器只需要向這里面添加就好了,而這個(gè)剛好是由DelegatingWebMvcConfiguration實(shí)現(xiàn)了。

    @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,調(diào)用addArgumentResolvers
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addArgumentResolvers(argumentResolvers);
        }
    }
  
    // 遍歷WebMvcConfigurer,調(diào)用addReturnValueHandlers
    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addReturnValueHandlers(returnValueHandlers);
        }
}

這個(gè)XXXComposite模式是不是有點(diǎn)似曾相似,沒(méi)錯(cuò),前面HandlerMethodArgumentResolverComposite啥的,都是和這個(gè)一個(gè)套路,將所有的WebMvcConfigurer配置類都組合到一起,做增強(qiáng)處理,同時(shí)自己也繼承了WebMvcConfigurer。
而這個(gè)configurersDelegatingWebMvcConfiguration.setConfigurers就將Spring中的所有WebMvcConfigurer都注入了。

所以,當(dāng)調(diào)用WebMvcConfigurationSupport.getArgumentResolvers的時(shí)候,就會(huì)調(diào)用到DelegatingWebMvcConfigurationaddArgumentResolvers,然后調(diào)用到我們自己的代碼:

    @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());
            }
        };
    }

最終將這個(gè)參數(shù)解析器放入到WebMvcConfigurationSupportargumentResolvers中。

那么到這里,我們已經(jīng)將我們自己定義的參數(shù)解析器放入到了處理器適配器RequestMappingHandlerAdapter的customArgumentResolvers中了。

4.RequestMappingHandlerAdapter中的參數(shù)解析器

上面的第三步,我們已經(jīng)將CurrentUserArgumentResolver這個(gè)自定義的參數(shù)解析器放到了RequestMappingHandlerAdaptercustomArgumentResolvers中了。接下來(lái),這個(gè)RequestMappingHandlerAdapter就要初始化了。

可以看到RequestMappingHandlerAdapter是實(shí)現(xiàn)InitializingBean,所以會(huì)進(jìn)入afterPropertiesSet方法

@Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
        initControllerAdviceCache();
         // 參數(shù)解析器
        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);
        }
    }

這里有g(shù)etDefaultArgumentResolvers(),然后獲取出來(lái)了以后,創(chuàng)建一個(gè)HandlerMethodArgumentResolverComposite,賦值給argumentResolvers
可以看下這個(gè)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;
    }

這里可以看到其實(shí)默認(rèn)的參數(shù)解析器是有很多的,比如處理@RequestParam的,@PathVariable的,以及@RequestBody的,我們的自定義會(huì)在getCustomArgumentResolvers()取出,然后加入進(jìn)去。返回值參數(shù)處理器,也是類似的。
所以這里也有小的知識(shí)點(diǎn):就是我們自己定義的參數(shù)解析器順序是比較靠后的,在參數(shù)解析的時(shí)候,都是優(yōu)先遍歷放在前面的參數(shù)解析器。

最終我們將所有的參數(shù)解析器都放在了RequestMappingHandlerAdapter的argumentResolvers中了。
至此,我們的初始化就以及完成了,后面就是請(qǐng)求進(jìn)來(lái)后,找解析器的工作了。

5.使用參數(shù)解析器

當(dāng)前面初始化好了,也可以接受請(qǐng)求的時(shí)候,這個(gè)時(shí)候請(qǐng)求進(jìn)來(lái)了。

根據(jù)之前的流程分析,這個(gè)請(qǐng)求會(huì)從DispatcherSerlvet有的doDispatch一直調(diào)用,然后到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) {
          // 設(shè)置參數(shù)解析器到`ServletInvocableHandlerMethod `
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            // 設(shè)置返回值處理器到`ServletInvocableHandlerMethod `
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
            // 調(diào)用
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

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

上面的代碼可以看到這個(gè)參數(shù)解析器被設(shè)置到了ServletInvocableHandlerMethod中,這個(gè)類我們之前也分析了。
它就是一個(gè)可以對(duì)這次請(qǐng)求進(jìn)行參數(shù)解析,調(diào)用處理方法,最后對(duì)返回值解析的類。

然后我們一路跟進(jìn)到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變量,這個(gè)變量就是之前的RequestMappingHandlerAdapter的argumentResolvers,然后他就調(diào)用每個(gè)參數(shù)解析器的supportsParameter,判斷是否支持,支持了就調(diào)用resolveArgument來(lái)獲得這個(gè)解析后的參數(shù)。

所以到這里,我們就分析完成了HandlerMethodArgumentResolver是如何加載,以及如何被使用的。

6.總結(jié)

1.SpringBoot加載WebMvcAutoConfiguration

2.調(diào)用EnableWebMvcConfiguration,然后調(diào)用父類的DelegatingWebMvcConfiguration,將自定義配置WebMvcConfigurer存放到WebMvcConfigurerComposite

3.調(diào)用requestMappingHandlerAdapter,然后調(diào)用到頂級(jí)父類WebMvcConfigurationSupport的requestMappingHandlerAdapter來(lái)創(chuàng)建這個(gè)RequestMappingHandlerAdapter

4.調(diào)用RequestMappingHandlerAdapter的getArgumentResolvers,然后會(huì)調(diào)用DelegatingWebMvcConfiguration里面的WebMvcConfigurerComposite,最終回調(diào)自定義的WebMvcConfigurer,將HandlerMethodArgumentResolver參數(shù)解析器加入到RequestMappingHandlerAdapter中

5.請(qǐng)求進(jìn)來(lái)后,從DispatcherSerlvet一直調(diào)用到RequestMappingHandlerAdapter的invokeHandlerMethod,
然后創(chuàng)建了一個(gè)ServletInvocableHandlerMethod,并且將WebMvcConfigurerComposite放到它的里面

6.到ServletInvocableHandlerMethod的getMethodArgumentValues里,將處理方法的所以參數(shù)都取出來(lái),循環(huán)遍歷判斷參數(shù)解析器是否支持,支持的話就解析

7.后續(xù)

前面可以看到了這個(gè)HandlerMethodArgumentResolver是有非常多的種類的,后續(xù)我們會(huì)對(duì)這些種類進(jìn)行一些分析,特別是RequestResponseBodyMethodProcessor,這個(gè)是比較復(fù)雜的,它不僅把參數(shù)解析器實(shí)現(xiàn)了,還把返回值處理器也給實(shí)現(xiàn)了,內(nèi)部用HttpMessageConverter,實(shí)現(xiàn)了多種數(shù)據(jù)轉(zhuǎn)換器,其中內(nèi)置包含了String,gson,Jackson等等的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容