在Spring MVC中我们会发现一个特别便利的一个小功能,那就是在Controller层映射的方法上
Spring会自动帮我们注入参数,帮我们初始化对象。
比如常用的:
@RequestParam :取querystring 当中的参数
@PathVariable :取 在@RequestMapping 中定义的占位符中的参数(/test/{id})
@RequestBody : 取request 这个消息体 一般用(String,byte[] 来接)
等等,更多的就不一一列举了,可以参看 这个包下的注解
(org.springframework.web.bind.annotation)
那么还有一些不需要注解的:如
Form(此Form 为 任意JavaBean 对象,Spring 会将相关请求参数自动注入) 等等。如此智能的体验,实在是太方便了。那么它们又是怎么工作的呢?
在比如一个业务场景,要是我想通过这种方式来自动注入登录用户,那么Spring 的参数注入支不支持自定义注入呢?带着这些好奇心我们来看它的大致工作流程。
其大致流程是 DispaterServlet 接收请求,并开始确定本次请求的 handler。Handler 中确定一个 ServletInvocableHandlerMethod,并初始化一些上下文参数。
ServletInvocableHandlerMethod 开始执行调用,在调用前确定和实例化该方法需要的参数。确定和实例化调用前需要的参数,使用到了一个List
那我们来看看这个接口
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
为了节约篇幅,我将注释给删掉了。这个接口由两个类构成,一个方法 supportsParameter用来确定方法上的参数,当前这个Resolver 可不可以处理它,如果支持那么就去处理。
另一个方法 resolveArgument 就很明显了 就是在 supportsParameter 为true 的时候,执行相关处理业务。
看到这里我们可以看出Spring MVC 中方法参数的自动注入是由实现了 HandlerMethodArgumentResolver 的类来完成的,那么文章开始提到的内嵌功能 又是怎么来的呢?聪明的朋友 不难想到,Spring 中肯定初始化了一些默认的resolver。对的,就是这样。我们可在
RequestMappingHandlerAdapter 中找到一个私有方法 getDefaultArgumentResolvers();里面展示了Spring内嵌的Resolver。
先把这段代码贴一下,大家就知道哪些功能是内嵌的了
private List getDefaultArgumentResolvers() {
List resolvers = new ArrayList();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers;
}
OK,那么怎么实现刚刚提到的自动注入登录用户这个功能呢?
首先我们来思考要实现这个功能需要哪几步,答案是两步,
1.我们需要有一个 处理这个业务的 resolver
2. 把这个resolver 给Spring 容器
好我们先来完成第一步,创建一个类 继承 HandlerMethodArgumentResolver ,如:
public class LoginUserArugmentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
/**
* 在这里可以是 通过注解方式(使用注解的或更加的灵活,但需要多创建一个相应的注解)
* ,也可以是直接通过判断class 的方式
*/
return parameter.hasParameterAnnotation(UserLogined.class)//通过注解方式
|| parameter.getParameterType() == User.class;//直接判断 类型方式
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer
, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
/**
* 到了这里说明 :方法参数中有匹配的情况那么我们可以 获得用户了
* ,这里只是简单的演示,取用户的操作
*/
Object user = webRequest.getNativeRequest(HttpServletRequest.class).
getSession().getAttribute("currentUser");
//如果有必要的话,可以在 用户为空的情况下抛出异常
/**
* if(user==null){
* throw new UserNotFoundException();
* }
*/
/**
* Tips: 在自定义参数注入中,有时也需要在路径中取出参数的情况,就像使用@PathVariable一样,
* 我们可以使用以下这行代码来取,Spring把路径中的参数和值封装到了一个Map里面
* ,并放进了Request中
* Map uriTemplateVars =
* (Map) webRequest.getAttribute(
* HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
*/
return user;
}
}
Ok 我们有了上面这个Resolver 后,就可以开始第二步了 将它交给Spring。
这里面提供两个版本的方式 第一种是常见的用到xml的方式,可以在 ServletContext.xml(Servlet级别的上下文) 中进行配置:
@EnableWebMvc@Configuration@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan({"com.mycompany.controller"})
public class ServletContextBoot extends WebMvcConfigurerAdapter{
@Override
public void addArgumentResolvers (List argumentResolvers) {
//此行 需要调用方法,这样Spring会帮你完成装配工作
argumentResolvers.add(loginUserArugmentResolver());
}
@Bean
public LoginUserArugmentResolver loginUserArugmentResolver() {
return new LoginUserArugmentResolver();
}
}