Spring MVC 自定义方法参数注入

Spring MVC中我们会发现一个特别便利的一个小功能,那就是在Controller层映射的方法上

Spring会自动帮我们注入参数,帮我们初始化对象。

比如常用的:

  @RequestParam  :取querystring 当中的参数

  @PathVariable  :取 在@RequestMapping 中定义的占位符中的参数(/test/{id})

  @RequestBody  : 取request 这个消息体 一般用(String,byte[] 来接)

等等,更多的就不一一列举了,可以参看 这个包下的注解

org.springframework.web.bind.annotation)

那么还有一些不需要注解的:如

  • HttpServletRequest
  • HttpServletResponse
  • MultipartFile
  • MultipartRequest

  Form(此Form 为 任意JavaBean 对象,Spring 会将相关请求参数自动注入)  等等。如此智能的体验,实在是太方便了。那么它们又是怎么工作的呢? 

在比如一个业务场景,要是我想通过这种方式来自动注入登录用户,那么Spring 的参数注入支不支持自定义注入呢?带着这些好奇心我们来看它的大致工作流程。

其大致流程是 DispaterServlet 接收请求,并开始确定本次请求的 handler。Handler 中确定一个 ServletInvocableHandlerMethod,并初始化一些上下文参数。

 ServletInvocableHandlerMethod 开始执行调用,在调用前确定和实例化该方法需要的参数。确定和实例化调用前需要的参数,使用到了一个List.OK. 那么Spring 就是通过 HandlerMethodArgumentResolver 来识别和处理它能识别的参数了。


那我们来看看这个接口

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级别的上下文) 中进行配置:


   
         
              
         
    

还是一种就是 Spring4.X  中通过 class  来配置 Sevlet 相关信息的方式 :

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

原文来自

你可能感兴趣的:(Spring)