zoukankan      html  css  js  c++  java
  • Spring自定义argumentResolver参数解析器

    在一个web程序中,当一个HTTP请求进来时,会被容器处理进而转换成一个servlet请求。
    http请求所携带的数据,虽然是格式化的但是无类型;而java作为强类型语言,同时为了健壮性考虑,必然要有完善的类型约束。
    当然,那么,将数据从servlet请求中转换到java中,一个很原始的方式是手动处理。
    幸好,Spring MVC通过以注解往函数添加额外信息的方式,使得上述的数据转换过程能够交由框架自动处理。
    从一个角度去看,Controller中的函数声明及注解定义了此HTTP请求的数据格式和类型,也即规定了对外部暴露的以http协议展现的接口。
    不过,有些时候内置注解无法满足需求的情况。这个时候,就需要自定义自己的注解以声明参数的格式。

    一、自定义注解

    现在假设我们需要自定义一种数据,叫做userId。
    当一个http请求进入时,我们期望的效果是框架从session取数据,并且放入到controller对应的参数中。
    现在,定义了一个叫做UserId的注解:

    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface UserId {
        boolean required() default true;
    }
    
    1. ElementType.PARAMETER可以看出,这个注解是用于修饰参数的。
    2. 该注解的保留策略要设为RUNTIME,很显然,因为框架是运行时通过反射拿到注解信息的。
    3. 注解携带了个参数required,在这里,是个类似接口的声明;但是在后面,则要通过此信息决定解析器的行为。
    二、参数解析器

    首先看位于HandlerMethodArgumentResolver.java的这个接口。
    通过实现这个接口的类,就是解析器。按照我们的期望,它中间的函数应该能得到必要信息,从而按照自定义逻辑计算并返回一个值。

     public interface HandlerMethodArgumentResolver {
        boolean supportsParameter(MethodParameter parameter); 
        Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, 
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
    }
    
    1. MethodParameter是spring对被注解修饰过参数的包装,从其中能拿到参数的反射相关信息。
    2. supportsParameter传入一个参数,用以判断此参数是否能够使用该解析器。
    3. resolveArgument就是之前讨论的解析函数,传入必要信息,计算并返回一个值。
    4. 综合来看,框架会将每一个MethodParameter传入supportsParameter测试是否能够被处理,如果能够,就使用resolveArgument处理。
      对于我们的userId解析器,如下:
    public class UserIdArgumentResolver implements HandlerMethodArgumentResolver {
    
     public static final String SESSION_USER_CLIENT_ID = "_session_user_clientId";
        @Getter
        @Setter
        private String noLoginMessage = "未登录!"; 
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(UserId.class);
        }
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
            WebDataBinderFactory binderFactory) throws Exception {
         Annotation[] annotations = parameter.getParameterAnnotations();
            // 逐一处理
            for (Annotation annotation : annotations) {
                // userId 
                if (annotation instanceof UserId) {
                    return resolveUserId((UserId) annotation, parameter, mavContainer, webRequest, binderFactory);
                }
            }
            return null;
        }
        private Object resolveUserId(ClientId annotation, MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest,
            WebDataBinderFactory binderFactory) {
    
            Object attribute = webRequest.getAttribute(SESSION_USER_CLIENT_ID, RequestAttributes.SCOPE_SESSION);
    
            if (attribute == null && annotation.required()) {
                throw new NoLoginException(noLoginMessage);
            }
            return attribute;
        }
    }
    
    1. supportsParameter的实现可以看出,只有被@UserId注解修饰的参数才能被此解析器处理。
    2. 如果参数被@UserId修饰,就会由resolveArgument计算出参数的值。从resolveArgument的实现看出,它从session中取数据作为参数的值。

    这个NativeWebRequest是一个关键点,看名字像是能拿到http请求的数据,粗略查询资料可知道,可从中拿到HttpServletRequest

     HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
    
    三、注册解析器

    最后,该把我们的解析器设置到spring MVC中去了:

     @Configuration
    public class WebConfig {
        @Bean
        public WebMvcConfigurer webMvcConfigurer() {
            return new WebMvcConfigurerAdapter() {
                @Override
                public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
                    argumentResolvers.add(new UserIdArgumentResolver());
                }
            };
        }
    }
    

    通过定义WebMvcConfigurer这个Bean能够自定义配置。
    看起来像是会把默认配置覆盖似的,不过实际上只是会和默认配置合并,大胆使用。
    最后,我们就能使用@UserId了

    @RequestMapping(value = "/api/user", method = RequestMethod.POST)
    @ResponseBody
    public ResponseDTO user(@UserId String userId) {
        return new ResponseDTO(new User());
    }
    
    四、更多信息

    通过自定义参数解析器这种方式,我们能够灵活的实现各种自定义的需求。
    上面举的例子是业务相关的,再举一个纯粹和接口格式有关系的例子:
    之前工作中发现前端的axios库默认的POST方式会将JSON格式的数据作为HTTP请求体传递。
    显然后端如果按照标准HTTP协议的方式解析肯定是会出问题的。
    那么解决方案出了前端配置axios库之外,理论上,后端也可以通过自定义参数解析器的方式,来进行扩展。
    实际上,spring MVC内置的参数注解是以同以上的方式实现的。以下代码位于RequestMappingHandlerAdapter.java:

    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
            List<HandlerMethodArgumentResolver> resolvers = new ArrayList();
            resolvers.add(new RequestParamMethodArgumentResolver(this.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(this.getMessageConverters(), this.requestResponseBodyAdvice));
            resolvers.add(new RequestPartMethodArgumentResolver(this.getMessageConverters(), this.requestResponseBodyAdvice));
            resolvers.add(new RequestHeaderMethodArgumentResolver(this.getBeanFactory()));
            resolvers.add(new RequestHeaderMapMethodArgumentResolver());
            resolvers.add(new ServletCookieValueMethodArgumentResolver(this.getBeanFactory()));
            resolvers.add(new ExpressionValueMethodArgumentResolver(this.getBeanFactory()));
            resolvers.add(new SessionAttributeMethodArgumentResolver());
            resolvers.add(new RequestAttributeMethodArgumentResolver());
            resolvers.add(new ServletRequestMethodArgumentResolver());
            resolvers.add(new ServletResponseMethodArgumentResolver());
            resolvers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice));
            resolvers.add(new RedirectAttributesMethodArgumentResolver());
            resolvers.add(new ModelMethodProcessor());
            resolvers.add(new MapMethodProcessor());
            resolvers.add(new ErrorsMethodArgumentResolver());
            resolvers.add(new SessionStatusMethodArgumentResolver());
            resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
            if (this.getCustomArgumentResolvers() != null) {
                resolvers.addAll(this.getCustomArgumentResolvers());
            }
            resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), true));
            resolvers.add(new ServletModelAttributeMethodProcessor(true));
            return resolvers;
        }
    技术小白,努力学习。
  • 相关阅读:
    POJ 3259 Wormholes【BellmanFord】
    POJ 2960 SNim【SG函数的应用】
    ZOJ 3578 Matrixdp水题
    HDU 2897 邂逅明下【bash博弈】
    BellmanFord 算法及其优化【转】
    【转】几个Java的网络爬虫
    thinkphp 反字符 去标签 自动加点 去换行 截取字符串 冰糖
    php 二维数组转 json文本 (jquery datagrid 数据格式) 冰糖
    PHP 汉字转拼音(首拼音,所有拼音) 冰糖
    设为首页与加入收藏 兼容firefox 冰糖
  • 原文地址:https://www.cnblogs.com/mei0619/p/11543709.html
Copyright © 2011-2022 走看看