zoukankan      html  css  js  c++  java
  • HandlerMethodArgumentResolver[2]-Map参数类型和固定参数类型

    参考/原地址:HandlerMethodArgumentResolver(二):Map参数类型和固定参数类型【享学Spring MVC】 - 云+社区 - 腾讯云 (tencent.com)

    前面介绍了Spring MVC用于处理入参的处理器:HandlerMethodReturnValueHandler它的作用,以及介绍了最为常用的两个参数处理器子类:PathVariableMethodArgumentResolverRequestParamMethodArgumentResolver。

    第一类:基于Name(续)

    RequestHeaderMethodArgumentResolver

    @RequestHeader注解,可以把Request请求header部分的值绑定到方法的参数上。

    public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
    
        // 必须标注@RequestHeader注解,并且不能,不能,不能是Map类型
        // 有的小伙伴会说:`@RequestHeader Map headers`这样可以接收到所有的请求头啊
        // 其实不是本类的功劳,是`RequestHeaderMapMethodArgumentResolver`的作用
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return (parameter.hasParameterAnnotation(RequestHeader.class) &&
                    !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
        }
    
        // 理解起来很简单:可以单值,也可以List/数组
        @Override
        @Nullable
        protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
            String[] headerValues = request.getHeaderValues(name);
            if (headerValues != null) {
                return (headerValues.length == 1 ? headerValues[0] : headerValues);
            } else {
                return null;
            }
        }
    }

    此处理器能处理的是我们这么来使用:

        @ResponseBody
        @GetMapping("/test")
        public Object test(@RequestHeader("Accept-Encoding") String encoding,
                           @RequestHeader("Accept-Encoding") List<String> encodingList) {
            System.out.println(encoding);
            System.out.println(encodingList);
            return encoding;
        }

    请求头截图:

    结果打印(集合封装成功了,证明逗号分隔是可以被封装成集合/数组的):
    gzip, deflate, br
    [gzip, deflate, br]

    Tip:注解指定的value值(key值)是区分大小写的

    RequestAttributeMethodArgumentResolver

    处理必须标注有@RequestAttribute注解的参数,原理说这一句话就够了。

    return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);

    SessionAttributeMethodArgumentResolver

    同上(注解不一样,scope不一样而已)

    AbstractCookieValueMethodArgumentResolver(抽象类)

    对解析标注有@CookieValue的做了一层抽象,子类负责从request里拿值(该抽象类不和请求域绑定)。

    public abstract class AbstractCookieValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
        ...
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(CookieValue.class);
        }    
        @Override
        protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
            throw new MissingRequestCookieException(name, parameter);
        }
        ... // 并木有实现核心resolveName方法
    }

    ServletCookieValueMethodArgumentResolver

    指定了从HttpServletRequest去拿cookie值。

    public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValueMethodArgumentResolver {
        private UrlPathHelper urlPathHelper = new UrlPathHelper();
        ...
        public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
            this.urlPathHelper = urlPathHelper;
        }
    
        @Override
        @Nullable
        protected Object resolveName(String cookieName, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
            HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
            Assert.state(servletRequest != null, "No HttpServletRequest");
    
            // 工具方法,底层是:request.getCookies()
            Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
            // 如果用javax.servlet.http.Cookie接受值,就直接返回了
            if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
                return cookieValue;
            } else if (cookieValue != null) { // 否则返回cookieValue
                return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
            } else {
                return null;
            }
        }
    }

    一般我们这么来用:

    @ResponseBody
    @GetMapping("/test")
    public Object test(@CookieValue("JSESSIONID") Cookie cookie,
                       @CookieValue("JSESSIONID") String cookieValue) {
        System.out.println(cookie);
        System.out.println(cookieValue);
        return cookieValue;
    }

    MatrixVariableMethodArgumentResolver

    标注有@MatrixVariable注解的参数的处理器。Matrix:矩阵,这个注解是Spring3.2新提出来的,增强Restful的处理能力(配合@PathVariable使用),比如这类URL的解析就得靠它:/owners/42;q=11/pets/21;s=23;q=22

    关于@MatrixVariable它的使用案例,我找了两篇靠谱文章给你参考: 参考一 参考二

    public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
        // @MatrixVariable注解是必须的。然后既能处理普通类型,也能处理Map
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
                return false;
            }
            if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
                return (matrixVariable != null && StringUtils.hasText(matrixVariable.name()));
            }
            return true;
        }
        ...
    }

    ExpressionValueMethodArgumentResolver

    它用于处理标注有@Value注解的参数。对于这个注解我们太熟悉不过了,没想到在web层依旧能发挥作用。本文就重点来会会它

    通过@Value让我们在配置文件里给参数赋值,在某些特殊场合(比如前端不用传,但你想给个默认值,这个时候用它也是一种方案)

    说明:这就相当于在Controller层使用了@Value注解,其实我是不太建议的。因为@Value建议还是只使用在业务层

    public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
        // 唯一构造函数  支持占位符、SpEL
        public ExpressionValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
            super(beanFactory);
        }
    
        //必须标注有@Value注解
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(Value.class);
        }
    
        @Override
        protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
            Value ann = parameter.getParameterAnnotation(Value.class);
            return new ExpressionValueNamedValueInfo(ann);
        }
        private static final class ExpressionValueNamedValueInfo extends NamedValueInfo {
            // 这里name传值为固定值  因为只要你的key不是这个就木有问题
            // required传固定值false
            // defaultValue:取值为annotation.value() --> 它天然支持占位符和SpEL嘛
            private ExpressionValueNamedValueInfo(Value annotation) {
                super("@Value", false, annotation.value());
            }
        }
    
        // 这里恒返回null,因此即使你的key是@Value,也是不会采纳你的传值的
        @Override
        @Nullable
        protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
            // No name to resolve
            return null;
        }
    }

    根本原理其实只是利用了defaultValue支持占位符和SpEL的特性而已。给个使用示例:

    // 在MVC子容器中导入外部化配置
    @Configuration
    @PropertySource("classpath:my.properties") // 此处有键值对:test.myage = 18
    @EnableWebMvc
    public class WebMvcConfig extends WebMvcConfigurerAdapter { ... }
    
        @ResponseBody
        @GetMapping("/test")
        public Object test(@Value("#{T(Integer).parseInt('${test.myage:10}') + 10}") Integer myAge) {
            System.out.println(myAge);
            return myAge;
        }
    }

    请求:/test,打印:28。 注意:若你写成@Value("#{'${test.myage:10}' + 10},那你得到的答案是:1810(成字符串拼接了)。

    另外,我看到网上有不少人说如果把这个@PropertySource("classpath:my.properties")放在根容器的config文件里导入,controller层就使用@Value/占位符获取不到值了,其实这是**不正确**的。理由如下:(可略过,SpringBoot是单个容器,不存在父子容器)

    Spring MVC子容器在创建时:initWebApplicationContext()

    if (cwac.getParent() == null) {
        cwac.setParent(rootContext); // 设置上父容器(根容器)
    }
    
    AbstractApplicationContext:如下代码
        // 相当于子容器的环境会把父容器的Enviroment合并进来
        @Override
        public void setParent(@Nullable ApplicationContext parent) {
            this.parent = parent;
            if (parent != null) {
                Environment parentEnvironment = parent.getEnvironment();
                if (parentEnvironment instanceof ConfigurableEnvironment) {
                    getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
                }
            }
        }
        
    AbstractEnvironment:merge()方法如下
        @Override
        public void merge(ConfigurableEnvironment parent) {
            // 完全的从parent里所有的PropertySources里拷贝一份进来
            for (PropertySource<?> ps : parent.getPropertySources()) {
                if (!this.propertySources.contains(ps.getName())) {
                    this.propertySources.addLast(ps);
                }
            }
            ...    
        }
    }

    这就是为什么说即使你是在根容器里使用的@PropertySource导入的外部资源,子容器也可以使用的原因(因为子容器会把父环境给merge一份过来)。

    但是,但是,但是:如果你是使用形如PropertyPlaceholderConfigurer这种方式导进来的,那是会有容器隔离效应的

    第二类:参数类型是Map

    这类解析器我认为是对第一类的有些处理器的一种补充,它依赖上面的相关注解。 你是否想过通过@RequestParam一次性全给封装进一个Map里,然后再自己分析?同样的本类处理器给@RequestHeader@PathVariable@MatrixVariable都赋予了这种能力

    PathVariableMapMethodArgumentResolver

    public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
    
        // 必须标注@PathVariable注解  并且类型是Map,并且注解不能有value值
        // 处理情况和PathVariableMethodArgumentResolver形成了互补
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
            return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
                    !StringUtils.hasText(ann.value()));
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
            ... // 处理上极其简单,把所有的路径参数使用Map装着返回即可
        }
    }

    RequestParamMapMethodArgumentResolver

    它依赖的方法是:HttpServletRequest#getParameterMap()MultipartRequest#getMultiFileMap()MultipartRequest#getFileMap()等,出现于Spring 3.1

    演示一把:

    @ResponseBody
    @GetMapping("/test")
    public Object test(@RequestParam Map<String,Object> params) {
        System.out.println(params);
        return params;
    }

    请求:/test?name=fsx&age=18&age=28。打印

    {name=fsx, age=18}

    从结果看出:

    1. 它不能传一key多值情况
    2. 若出现相同的key,以在最前面的key的值为准。
    3. Map实例是一个LinkedHashMap<String,String>实例

    RequestHeaderMapMethodArgumentResolver

    一次性把请求头信息都拿到:数据类型支出写MultiValueMap(LinkedMultiValueMap)/HttpHeaders/Map。实例如下:

    @ResponseBody
    @GetMapping("/test")
    public Object test(@RequestHeader Map<String, Object> headers) {
        headers.forEach((k, v) -> System.out.println(k + "-->" + v));
        return headers;
    }

    请求打印:

    host-->localhost:8080
    connection-->keep-alive
    cache-control-->max-age=0
    upgrade-insecure-requests-->1
    user-agent-->Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
    sec-fetch-mode-->navigate
    sec-fetch-user-->?1
    accept-->text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    sec-fetch-site-->none
    accept-encoding-->gzip, deflate, br
    accept-language-->zh-CN,zh;q=0.9
    cookie-->JSESSIONID=123456789

    不过强烈不建议直接使用Map,而是使用HttpHeaders类型。这么写@RequestHeader HttpHeaders headers,获取的时候更为便捷。

    MatrixVariableMapMethodArgumentResolver

    MapMethodProcessor

    它处理Map类型,但没有标注任何注解的情况,它的执行顺序是很靠后的,所以有点兜底的意思。

    public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return Map.class.isAssignableFrom(parameter.getParameterType());
        }
    
        // 处理逻辑非常简单粗暴:把Model直接返回~~~~
        @Override
        @Nullable
        public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
            return mavContainer.getModel();
        }
    }

    使用案例:略。

    这个处理器同时也解释了:为何你方法入参上写个Map、HashMap、ModelMap等等就可以非常便捷的获取到模型的值的原因

    第三类:固定参数类型

    参数比如是SessionStatus, ServletResponse, OutputStream, Writer, WebRequest, MultipartRequest, HttpSession, Principal, InputStream

    这种方式使用得其实还比较多的。比如平时我们需要用Servlet源生的API:HttpServletRequest, HttpServletResponse肿么办? 在Spring MVC内就特别特别简单,只需要在入参上声明:就可以直接使用

    ServletRequestMethodArgumentResolver

    // 它支持到的可不仅仅是ServletRequest,多到令人发指
    public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
    
        // 连Servlet 4.0的PushBuilder都支持了(Spring5.0以上版本支持的)
        @Nullable
        private static Class<?> pushBuilder;
        static {
            try {
                pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder",
                        ServletRequestMethodArgumentResolver.class.getClassLoader());
            } catch (ClassNotFoundException ex) {
                // Servlet 4.0 PushBuilder not found - not supported for injection
                pushBuilder = null;
            }
        }
    
        // 支持"注入"的类型,可谓多多益善
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            Class<?> paramType = parameter.getParameterType();
            return (WebRequest.class.isAssignableFrom(paramType) ||
                    ServletRequest.class.isAssignableFrom(paramType) || // webRequest.getNativeRequest(requiredType)
                    MultipartRequest.class.isAssignableFrom(paramType) ||
                    HttpSession.class.isAssignableFrom(paramType) || //request.getSession()
                    (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || //PushBuilderDelegate.resolvePushBuilder(request, paramType);
                    Principal.class.isAssignableFrom(paramType) || //request.getUserPrincipal()
                    InputStream.class.isAssignableFrom(paramType) || // request.getInputStream()
                    Reader.class.isAssignableFrom(paramType) || //request.getReader()
                    HttpMethod.class == paramType || //HttpMethod.resolve(request.getMethod());
                    Locale.class == paramType || //RequestContextUtils.getLocale(request)
                    TimeZone.class == paramType || //RequestContextUtils.getTimeZone(request)
                    ZoneId.class == paramType); //RequestContextUtils.getTimeZone(request);
        }
    }

    ServletResponseMethodArgumentResolver

    public class ServletResponseMethodArgumentResolver implements HandlerMethodArgumentResolver {
        // 它相对来说很比较简单
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            Class<?> paramType = parameter.getParameterType();
            return (ServletResponse.class.isAssignableFrom(paramType) || // webRequest.getNativeResponse(requiredType)
                    OutputStream.class.isAssignableFrom(paramType) || //response.getOutputStream()
                    Writer.class.isAssignableFrom(paramType)); //response.getWriter()
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
            // 这个判断放在这。。。
            if (mavContainer != null) {
                mavContainer.setRequestHandled(true);
            }
            ... 
        }
    }

    SessionStatusMethodArgumentResolver

    支持SessionStatus。值为:mavContainer.getSessionStatus();

    UriComponentsBuilderMethodArgumentResolver

    public class UriComponentsBuilderMethodArgumentResolver implements HandlerMethodArgumentResolver {
        // UriComponentsBuilder/ ServletUriComponentsBuilder
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            Class<?> type = parameter.getParameterType();
            return (UriComponentsBuilder.class == type || ServletUriComponentsBuilder.class == type);
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            return ServletUriComponentsBuilder.fromServletMapping(request);
        }
    }

    通过UriComponentsBuilder来得到URL的各个部分,以及构建URL都是非常的方便的。

    RedirectAttributesMethodArgumentResolver

    和重定向属性RedirectAttributes相关。

    public class RedirectAttributesMethodArgumentResolver implements HandlerMethodArgumentResolver {
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
            ModelMap redirectAttributes;
    
            // 把DataBinder传入到RedirectAttributesModelMap里面去
            if (binderFactory != null) {
                DataBinder dataBinder = binderFactory.createBinder(webRequest, null, DataBinder.DEFAULT_OBJECT_NAME);
                redirectAttributes = new RedirectAttributesModelMap(dataBinder);
            } else {
                redirectAttributes  = new RedirectAttributesModelMap();
            }
            mavContainer.setRedirectModel(redirectAttributes);
            return redirectAttributes;
        }
    }

    如果涉及到重定向:多个视图间传值,使用它还是比较方便的。

    ModelMethodProcessor

    允许你入参里写:org.springframework.ui.ModelRedirectAttributesRedirectAttributesModelMapConcurrentModelExtendedModelMap等等

    ModelAttributeMethodProcessor

    一个特殊的处理器:ModelAttributeMethodProcessor:主要是针对 被 @ModelAttribute注解修饰且不是普通类型(通过 !BeanUtils.isSimpleProperty来判断)的参数。

    public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
    
        // 标注有@ModelAttribute它会处理
        // 若没有标注(只要不是“简单类型”),它也会兜底处理
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
                    (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
        }
    }

    关于@ModelAttribute这块的使用,参见这里

  • 相关阅读:
    Python-os
    Python-字典Dict
    Linux下使用Apache搭建Web网站服务器
    Linux中FTP安装与配置
    第16章 广域网
    第15章 IPv6
    第14章 思科无线技术
    第13章 网络地址转换NAT
    第12章 安全
    第11章 虚拟局域网
  • 原文地址:https://www.cnblogs.com/chenxingyang/p/15555071.html
Copyright © 2011-2022 走看看