SpringMVC源码研究之注解mvc:argument-resolvers

1. 起因

最近在看开涛大神的《跟我学Shiro》系列文章的源码时,发现这样的一个技巧:

  1. 通过一个继承自Shiro自定义PathMatchingFilterSysUserFilter类将当前用户的信息推入到本次请求Request实例中;
  2. 然后使用自定义注解@CurrentUser和和继承自HandlerMethodArgumentResolverCurrentUserMethodArgumentResolver来参与SpringMVC的参数解析逻辑。在检测到用户在Controller层的方法参数中使用了注解@CurrentUser时,就将前面检索到的用户信息注入到被注解@CurrentUser修饰的方法参数中。
  3. 最终实现调用端获取当前登录用户信息的透明化。

本人虽然之前有过一篇关于<mvc:annotation-driven>的博客;但很明显是非常地不完善,所以这篇文章也等同于对那篇博客的补充。

2. 相关配置

先来看看相关配置和使用

  1. spring-mvc.xml

    <mvc:annotation-driven>
    	<mvc:argument-resolvers>
    	    <bean class="xxxx.yyyy.zzzzz.CurrentUserMethodArgumentResolver"/>
    	</mvc:argument-resolvers>
    <mvc:annotation-driven>
    
  2. controller层

    @RequestMapping(value = "/getUser", method = { RequestMethod.POST})
    @ResponseBody
    public ResponseBean<LoginUser> getUser(@CurrentUser LoginUser user) {
    	ResponseBean<LoginUser> of = ResponseBean.of(user);
    	return of;
    }
    

3. 原理

让我们再次回到解析<mvc:annotation-driven />AnnotationDrivenBeanDefinitionParser中;这次我们关注的重心就是解析<mvc:argument-resolvers>的部分。 通过跟踪,我们发现,我们配置的argument-resolvers最终是被注入到了 RequestMappingHandlerAdapter 类的customArgumentResolvers字段值中

而在 RequestMappingHandlerAdaptergetDefaultArgumentResolvers方法中就可以看到Spring对HandlerMethodArgumentResolver的添加顺序(也就是执行时的查找顺序):

  1. 先添加Spring内置的,
  2. 再添加用户自定义的,
  3. 最后是查找Spring容器里的
  4. 到4.3.12版本为止,没有提供排序功能!

而且RequestMappingHandlerAdapter实现了InitializingBean接口,而在其实现的InitializingBean接口的afterPropertiesSet方法中,正好对上面的getDefaultArgumentResolvers方法进行了调用,将返回值使用组合模式赋值给了RequestMappingHandlerAdapter类的类级字段argumentResolvers(其余的还有对getDefaultInitBinderArgumentResolversgetDefaultReturnValueHandlers方法的调用。)

关于RequestMappingHandlerAdapter类的invokeHandlerMethod方法,请参见下本人博客SpringMVC源码研究之DispatcherServlet处理请求 中的第5.1小节。

RequestMappingHandlerAdapter类的invokeHandlerMethod方法中,正是将自身收集到的argumentResolvers传递给ServletInvocableHandlerMethod实例,而ServletInvocableHandlerMethod实例会最终调用继承自基类InvocableHandlerMethodgetMethodArgumentValues方法中;从而应用上我们自定义的CurrentUserMethodArgumentResolver类;

4. 应用

之所以会研究这个的原因,本人在借鉴此思路时,将LoginUser类画蛇添足地继承了Shiro中的MapContext类。因为觉得MapContext类的思路很好,和本人非常崇拜的梁飞大神在httl框架中的Context如出一辙。结果聪明反被聪明误了!

这里详细解释下原因:

以下是Spring中的ArgumentResolver集合(在HandlerMethodArgumentResolverComposite类中的argumentResolvers字段中进行存储,这里有一个技巧是在内部还使用了另外一个字段argumentResolverCache进行运行时缓存,对进行过解析的方法直接调用缓存,不再进行二次解析):

[org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@5daf54dc, 
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@21ec6791, 
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@278371fd, 
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@26b0ad4f, 
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@19423161, 
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@48b7e43b, 
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@20e902c9, 
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@21b5eb00, 
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@2e575e4a, 
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@69ef640a, 
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@639102db, 
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@4552aaee, 
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@365412d0, 
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@499f318d, 
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@726e45bd, org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@25a4b5fc, 
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@60a7c6b2, 
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@26e601b1, 
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@46791f6a,
org.springframework.web.method.annotation.ModelMethodProcessor@39fd8c4b, 
org.springframework.web.method.annotation.MapMethodProcessor@64cd0145, // 注意这里, 它实现的supportsParameter方法在下方给出
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@3505adab, 
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@7ccd0bf, 
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@179caf99, 
xxxx.yyyy.zzzzz.CurrentUserMethodArgumentResolver@2fcc71eb,  // 这就是本人注册的, 用于推入当前用户信息
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@174b13d1, 
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@42020a49]

MapMethodProcessor 类的定义如下:

// MapMethodProcessor
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
<span class="token comment">// 只保留了所关心的部分。</span>

<span class="token annotation punctuation">&#64;Override</span>
<span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">supportsParameter</span><span class="token punctuation">(</span>MethodParameter parameter<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 关键就是这里了, 负责传入参数是Map类型的; 因为我将LoginUser继承自了Shiro的MapContext, 所以直接被这个类给拦截处理了</span>
	<span class="token keyword">return</span> Map<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">isAssignableFrom</span><span class="token punctuation">(</span>parameter<span class="token punctuation">.</span><span class="token function">getParameterType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token annotation punctuation">&#64;Override</span>
<span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">supportsReturnType</span><span class="token punctuation">(</span>MethodParameter returnType<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 返回值也是; </span>
	<span class="token keyword">return</span> Map<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">isAssignableFrom</span><span class="token punctuation">(</span>returnType<span class="token punctuation">.</span><span class="token function">getParameterType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

所以根本原因是**本人的LoginUser间接继承自Map, 所以最终被Spring内置的MapMethodProcessor拦截处理了。**因为Spring还没有提供Ordered接口的应用,解决方法就很清晰了:删除掉对MapContext的继承即可,改为直接实现Serializable接口。

5. 结束语

虽然经历了一番折腾,但最终走出焦油坑,回看SpringMVC对此的处理,体会其中控制权反转,职责委派,面向对象思维等的应用,确实有一种美感存乎其中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值