接之前的文章SpringMVC源码研究之注解 mvc:annotation-driven
最近在看开涛大神的《跟我学Shiro》系列文章的源码时,发现这样的一个技巧:
PathMatchingFilter
的SysUserFilter
类将当前用户的信息推入到本次请求Request
实例中;@CurrentUser
和和继承自HandlerMethodArgumentResolver
的CurrentUserMethodArgumentResolver
来参与SpringMVC的参数解析逻辑。在检测到用户在Controller层的方法参数中使用了注解@CurrentUser
时,就将前面检索到的用户信息注入到被注解@CurrentUser
修饰的方法参数中。本人虽然之前有过一篇关于
的博客;但很明显是非常地不完善,所以这篇文章也等同于对那篇博客的补充。
先来看看相关配置和使用
spring-mvc.xml
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="xxxx.yyyy.zzzzz.CurrentUserMethodArgumentResolver"/>
mvc:argument-resolvers>
<mvc:annotation-driven>
controller层
@RequestMapping(value = "/getUser", method = { RequestMethod.POST})
@ResponseBody
public ResponseBean<LoginUser> getUser(@CurrentUser LoginUser user) {
ResponseBean<LoginUser> of = ResponseBean.of(user);
return of;
}
让我们再次回到解析
的AnnotationDrivenBeanDefinitionParser
中;这次我们关注的重心就是解析
的部分。 通过跟踪,我们发现,我们配置的argument-resolvers
最终是被注入到了 RequestMappingHandlerAdapter
类的customArgumentResolvers
字段值中。
而在 RequestMappingHandlerAdapter
类getDefaultArgumentResolvers
方法中就可以看到Spring对HandlerMethodArgumentResolver
的添加顺序(也就是执行时的查找顺序):
而且RequestMappingHandlerAdapter
类实现了InitializingBean
接口,而在其实现的InitializingBean
接口的afterPropertiesSet
方法中,正好对上面的getDefaultArgumentResolvers
方法进行了调用,将返回值使用组合模式赋值给了RequestMappingHandlerAdapter
类的类级字段argumentResolvers
(其余的还有对getDefaultInitBinderArgumentResolvers
,getDefaultReturnValueHandlers
方法的调用。)
关于RequestMappingHandlerAdapter
类的invokeHandlerMethod
方法,请参见下本人博客SpringMVC源码研究之DispatcherServlet处理请求 中的第5.1小节。
在RequestMappingHandlerAdapter
类的invokeHandlerMethod
方法中,正是将自身收集到的argumentResolvers
传递给ServletInvocableHandlerMethod
实例,而ServletInvocableHandlerMethod
实例会最终调用继承自基类InvocableHandlerMethod
的getMethodArgumentValues
方法中;从而应用上我们自定义的CurrentUserMethodArgumentResolver
类;
之所以会研究这个的原因,本人在借鉴此思路时,将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 {
// 只保留了所关心的部分。
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 关键就是这里了, 负责传入参数是Map类型的; 因为我将LoginUser继承自了Shiro的MapContext, 所以直接被这个类给拦截处理了
return Map.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// 返回值也是;
return Map.class.isAssignableFrom(returnType.getParameterType());
}
所以根本原因是**本人的LoginUser间接继承自Map, 所以最终被Spring内置的MapMethodProcessor拦截处理了。**因为Spring还没有提供Ordered
接口的应用,解决方法就很清晰了:删除掉对MapContext
的继承即可,改为直接实现Serializable
接口。
虽然经历了一番折腾,但最终走出焦油坑,回看SpringMVC对此的处理,体会其中控制权反转,职责委派,面向对象思维等的应用,确实有一种美感存乎其中。