欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

HandlerMethodArgumentResolver(二):Map参数类型和固定参数类型【享学Spring MVC】

程序员文章站 2022-05-07 10:53:49
一个可以沉迷于技术的程序猿,wx加入加入技术群:fsx641385712 ......

每篇一句

黄金的导电性最好,为什么电脑主板还是要用铜?
飞机最快,为什么还有人做火车?
清华大学最好,为什么还有人去普通学校?
因为资源都是有限的,我们现实生活中必须兼顾成本与产出的平衡

前言

介绍了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;
    }

请求头截图:
HandlerMethodArgumentResolver(二):Map参数类型和固定参数类型【享学Spring MVC】
结果打印(集合封装成功了,证明逗号分隔是可以被封装成集合/数组的):

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;
    }

手动设置一个cookie值,然后请求
HandlerMethodArgumentResolver(二):Map参数类型和固定参数类型【享学Spring MVC】
控制台打印如下:

javax.servlet.http.cookie@401ef395
123456

tips:在现在restful风格下,cookie使用得是很少的了。一般用于提升用户体验方面~

matrixvariablemethodargumentresolver

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

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

// @since 3.2
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建议还是只使用在业务层~

// @since 3.1
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/占位符获取不到值了,其实这是不正确的。理由如下:

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

数据来源同上,只是参数类型是map

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

pathvariablemapmethodargumentresolver

// @since 3.2 晚一个版本号
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类型,但没有标注任何注解的情况,它的执行顺序是很靠后的,所以有点兜底的意思。

// @since 3.1
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);
    }
}

看到这你应该明白,以后你需要使用这些参数的话,直接在方法上申明即可,不需要自己再去get了,又是一种依赖注入的效果体现有木有~

servletresponsemethodargumentresolver

// @since 3.1
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

// @since 3.1
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相关。

// @since 3.1
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:主要是针对 被 @modelattribute注解修饰且不是普通类型(通过 !beanutils.issimpleproperty来判断)的参数。

// @since 3.1
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这块的使用,

总结

本文介绍完了四大类的前面三种类型,其中最为常用的是前两种类型的使用,希望大家可以掌握,和好好发挥~

相关阅读

handlermethodargumentresolver:controller入参自动封装器(将方法参数parameter解析为参数值)【享学spring mvc】
从原理层面掌握@modelattribute的使用(核心原理篇)【享学spring mvc】
从原理层面掌握@modelattribute的使用(使用篇)【享学spring mvc】

handlermethodargumentresolver(一):controller方法入参自动封装器(将参数parameter解析为值)【享学spring mvc】
handlermethodargumentresolver(二):map参数类型和固定参数类型【享学spring mvc】
handlermethodargumentresolver(三):基于httpmessageconverter消息转换器的参数处理器【享学spring mvc】

知识交流

==the last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~==

若对技术内容感兴趣可以加入wx群交流:java高工、架构师3群
若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群

==若对spring、springboot、mybatis等源码分析感兴趣,可加我wx:fsx641385712,手动邀请你入群一起飞==