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

Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析

程序员文章站 2023-11-16 16:24:04
概述 在SpringMVC的使用时,往往会用到@RequestBody和@ResponseBody两个注解,尤其是处理ajax请求必然要使用@ResponseBody注解。这两个注解对应着Controller方法的参数解析和返回值处理,开始时都是只知其用,不知原理。我们来看个例子。 第一个reque ......

概述

在springmvc的使用时,往往会用到@requestbody和@responsebody两个注解,尤其是处理ajax请求必然要使用@responsebody注解。这两个注解对应着controller方法的参数解析和返回值处理,开始时都是只知其用,不知原理。我们来看个例子。

@requestmapping("/requestbody")
public void requestbody(@requestbody string body, writer writer) throws ioexception{
    writer.write(body);
}

@requestmapping(value="/responsebody", produces="application/json")
@responsebody
public map<string, object> responsebody(){
    map<string, object> retmap = new hashmap<>();
    retmap.put("param1", "abc");
    return retmap;
}

第一个requestbody请求,使用@requestbody将http请求体转换成string类型,第二个responsebody请求,将map对象转换成json格式输出到http响应中。这两个请求方法没有什么特殊,就是一个在参数前加了@requestbody注解,一个在方法上加了@responsebody注解。而这两个注解是怎么完成http报文信息同controller方法中对象的转换的呢?

springmvc处理请求和响应时,支持多种类型的请求参数和返回类型,而此种功能的实现就需要对http消息体和参数及返回值进行转换,为此springmvc提供了大量的转换类,所有转换类都实现了httpmessageconverter接口。

public interface httpmessageconverter<t> {

    // 当前转换器是否能将http报文转换为对象类型
    boolean canread(class<?> clazz, mediatype mediatype);

    // 当前转换器是否能将对象类型转换为http报文
    boolean canwrite(class<?> clazz, mediatype mediatype);

    // 转换器能支持的http媒体类型
    list<mediatype> getsupportedmediatypes();

    // 转换http报文为特定类型
    t read(class<? extends t> clazz, httpinputmessage inputmessage)
            throws ioexception, httpmessagenotreadableexception;

    // 将特定类型对象转换为http报文
    void write(t t, mediatype contenttype, httpoutputmessage outputmessage)
            throws ioexception, httpmessagenotwritableexception;

}

httpmessageconverter接口定义了5个方法,用于将http请求报文转换为java对象,以及将java对象转换为http响应报文。

对应到springmvc的controller方法,read方法即是读取http请求转换为参数对象,write方法即是将返回值对象转换为http响应报文。springmvc定义了两个接口来操作这两个过程:参数解析器handlermethodargumentresolver和返回值处理器handlermethodreturnvaluehandler。

// 参数解析器接口
public interface handlermethodargumentresolver {

    // 解析器是否支持方法参数
    boolean supportsparameter(methodparameter parameter);

    // 解析http报文中对应的方法参数
    object resolveargument(methodparameter parameter, modelandviewcontainer mavcontainer,
            nativewebrequest webrequest, webdatabinderfactory binderfactory) throws exception;

}

// 返回值处理器接口
public interface handlermethodreturnvaluehandler {

    // 处理器是否支持返回值类型
    boolean supportsreturntype(methodparameter returntype);

    // 将返回值解析为http响应报文
    void handlereturnvalue(object returnvalue, methodparameter returntype,
            modelandviewcontainer mavcontainer, nativewebrequest webrequest) throws exception;

}

参数解析器和返回值处理器在底层处理时,都是通过httpmessageconverter进行转换。流程如下:

Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析

 

 springmvc为@requestbody和@responsebody两个注解实现了统一处理类requestresponsebodymethodprocessor,实现了handlermethodargumentresolver和handlermethodreturnvaluehandler两个接口。

 由上一篇文章我们可以知道,controller方法被封装成servletinvocablehandlermethod类,并且由invokeandhandle方法完成请求处理。

 

public void invokeandhandle(servletwebrequest webrequest, modelandviewcontainer mavcontainer,
        object... providedargs) throws exception {

    // 执行请求
    object returnvalue = invokeforrequest(webrequest, mavcontainer, providedargs);
    
    // 返回值处理
    try {
        this.returnvaluehandlers.handlereturnvalue(
                returnvalue, getreturnvaluetype(returnvalue), mavcontainer, webrequest);
    }
    catch (exception ex) {
        if (logger.istraceenabled()) {
            logger.trace(getreturnvaluehandlingerrormessage("error handling return value", returnvalue), ex);
        }
        throw ex;
    }
}

public object invokeforrequest(nativewebrequest request, modelandviewcontainer mavcontainer,
        object... providedargs) throws exception {
    // 参数解析
    object[] args = getmethodargumentvalues(request, mavcontainer, providedargs);
    // invoke controller方法
    object returnvalue = doinvoke(args);
    return returnvalue;
}

在invoke controller方法的前后分别执行了方法参数的解析和返回值的处理,我们分别来看。

参数解析

private object[] getmethodargumentvalues(nativewebrequest request, modelandviewcontainer mavcontainer,
        object... providedargs) throws exception {

    methodparameter[] parameters = getmethodparameters();
    object[] args = new object[parameters.length];

    // 遍历所有参数,逐个解析
    for (int i = 0; i < parameters.length; i++) {
        methodparameter parameter = parameters[i];
        parameter.initparameternamediscovery(this.parameternamediscoverer);
        args[i] = resolveprovidedargument(parameter, providedargs);
        if (args[i] != null) {
            continue;
        }

        // 参数解析器解析http报文到参数
        if (this.argumentresolvers.supportsparameter(parameter)) {
            args[i] = this.argumentresolvers.resolveargument(
                    parameter, mavcontainer, request, this.databinderfactory);
            continue;
        }
    }
    return args;
}

getmethodargumentvalues方法中的argumentresolvers就是多个handlermethodargumentresolver的集合体,supportsparameter方法寻找参数合适的解析器,resolveargument调用具体解析器的resolveargument方法执行。

我们从requestresponsebodymethodprocessor看看@requestbody的解析过程。requestresponsebodymethodprocessor的supportsparameter定义了它支持的参数类型,即必须有@requestbody注解。

public boolean supportsparameter(methodparameter parameter) {
    return parameter.hasparameterannotation(requestbody.class);
}

再来看resolveargument方法

public object resolveargument(methodparameter parameter, modelandviewcontainer mavcontainer,
        nativewebrequest webrequest, webdatabinderfactory binderfactory) throws exception {

    parameter = parameter.nestedifoptional();
    // 通过httpmessageconverter读取http报文
    object arg = readwithmessageconverters(webrequest, parameter, parameter.getnestedgenericparametertype());
    string name = conventions.getvariablenameforparameter(parameter);

    webdatabinder binder = binderfactory.createbinder(webrequest, arg, name);
    if (arg != null) {
        validateifapplicable(binder, parameter);
        if (binder.getbindingresult().haserrors() && isbindexceptionrequired(binder, parameter)) {
            throw new methodargumentnotvalidexception(parameter, binder.getbindingresult());
        }
    }
    mavcontainer.addattribute(bindingresult.model_key_prefix + name, binder.getbindingresult());

    return adaptargumentifnecessary(arg, parameter);
}

具体实现由httpmessageconverter来完成

protected <t> object readwithmessageconverters(httpinputmessage inputmessage, methodparameter parameter,
        type targettype) throws ioexception, httpmediatypenotsupportedexception, httpmessagenotreadableexception {

    ....

    try {
        inputmessage = new emptybodycheckinghttpinputmessage(inputmessage);

        for (httpmessageconverter<?> converter : this.messageconverters) {
            class<httpmessageconverter<?>> convertertype = (class<httpmessageconverter<?>>) converter.getclass();
            ....
            // 判断转换器是否支持参数类型
            if (converter.canread(targetclass, contenttype)) {
                if (inputmessage.getbody() != null) {
                    inputmessage = getadvice().beforebodyread(inputmessage, parameter, targettype, convertertype);
                    // read方法执行http报文到参数的转换
                    body = ((httpmessageconverter<t>) converter).read(targetclass, inputmessage);
                    body = getadvice().afterbodyread(body, inputmessage, parameter, targettype, convertertype);
                }
                else {
                    body = getadvice().handleemptybody(null, inputmessage, parameter, targettype, convertertype);
                }
                break;
            }
            ...
        }
    }
    catch (ioexception ex) {
        throw new httpmessagenotreadableexception("i/o error while reading input message", ex);
    }

    ....

    return body;
}

代码部分省略了,关键部分即是遍历所有的httpmessageconverter,通过canread方法判断转换器是否支持对参数的转换,然后执行read方法完成转换。

返回值处理

完成controller方法的调用后,在servletinvocablehandlermethod的invokeandhandle中,使用返回值处理器对返回值进行转换。

this.returnvaluehandlers.handlereturnvalue(
                returnvalue, getreturnvaluetype(returnvalue), mavcontainer, webrequest);

这里的returnvaluehandlers也是handlermethodreturnvaluehandler的集合体handlermethodreturnvaluehandlercomposite

public void handlereturnvalue(object returnvalue, methodparameter returntype,
        modelandviewcontainer mavcontainer, nativewebrequest webrequest) throws exception {

    // 选择合适的handlermethodreturnvaluehandler,如果没有用@resposebody注解和用了注解其返回值处理器肯定不同
    handlermethodreturnvaluehandler handler = selecthandler(returnvalue, returntype);
    if (handler == null) {
        throw new illegalargumentexception("unknown return value type: " + returntype.getparametertype().getname());
    }
    // 执行返回值处理
    handler.handlereturnvalue(returnvalue, returntype, mavcontainer, webrequest);
}

selecthandler方法遍历所有handlermethodreturnvaluehandler,调用其supportsreturntype方法选择合适的handlermethodreturnvaluehandler,然后调用其handlereturnvalue方法完成处理。

这里还是以requestresponsebodymethodprocessor来分析下@responsebody的处理,它的具体实现在abstractmessageconvertermethodprocessor抽象基类中。

Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析

public boolean supportsreturntype(methodparameter returntype) {
    return (annotatedelementutils.hasannotation(returntype.getcontainingclass(), responsebody.class) ||
            returntype.hasmethodannotation(responsebody.class));
}

requestresponsebodymethodprocessor要求方法上有@responsebody注解或者方法所在的controller类上有@responsebody的注解。这就是常常用@restcontroller注解代替@controller注解的原因,因为@restcontroller注解自带@responsebody。

handlereturnvalue方法实际也是调用httpmessageconverter来完成转换处理

public void handlereturnvalue(object returnvalue, methodparameter returntype,
        modelandviewcontainer mavcontainer, nativewebrequest webrequest)
        throws ioexception, httpmediatypenotacceptableexception, httpmessagenotwritableexception {

    mavcontainer.setrequesthandled(true);
    servletserverhttprequest inputmessage = createinputmessage(webrequest);
    servletserverhttpresponse outputmessage = createoutputmessage(webrequest);

    // 调用httpmessageconverter执行
    writewithmessageconverters(returnvalue, returntype, inputmessage, outputmessage);
}

protected <t> void writewithmessageconverters(t value, methodparameter returntype,
        servletserverhttprequest inputmessage, servletserverhttpresponse outputmessage)
        throws ioexception, httpmediatypenotacceptableexception, httpmessagenotwritableexception {

    ....

    if (selectedmediatype != null) {
        selectedmediatype = selectedmediatype.removequalityvalue();
        for (httpmessageconverter<?> messageconverter : this.messageconverters) {
            // 判断是否支持返回值类型,返回值类型很有可能不同,如string,map,list等
            if (messageconverter.canwrite(valuetype, selectedmediatype)) {
                outputvalue = (t) getadvice().beforebodywrite(outputvalue, returntype, selectedmediatype,
                        (class<? extends httpmessageconverter<?>>) messageconverter.getclass(),
                        inputmessage, outputmessage);
                if (outputvalue != null) {
                    addcontentdispositionheader(inputmessage, outputmessage);
                    // 执行返回值转换
                    ((httpmessageconverter) messageconverter).write(outputvalue, selectedmediatype, outputmessage);
                    ...
                }
                return;
            }
        }
    }
    ....
}

使用canwrite方法选择合适的httpmessageconverter,然后调用write方法完成转换。

Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析

 我们看看传入的参数 servletserverhttpresponse outputmessage = createoutputmessage(webrequest);

protected servletserverhttpresponse createoutputmessage(nativewebrequest webrequest) {
    //获取httpservletresponse
    httpservletresponse response = (httpservletresponse)webrequest.getnativeresponse(httpservletresponse.class);
    assert.state(response != null, "no httpservletresponse");
    return new servletserverhttpresponse(response);
}

public class servletserverhttpresponse implements serverhttpresponse {
    private final httpservletresponse servletresponse;
    private final httpheaders headers;
    private boolean headerswritten = false;
    private boolean bodyused = false;

    public servletserverhttpresponse(httpservletresponse servletresponse) {
        assert.notnull(servletresponse, "httpservletresponse must not be null");
        //将获取的httpservletresponse作为servletserverhttpresponse的属性值
        this.servletresponse = servletresponse;
        this.headers = new servletserverhttpresponse.servletresponsehttpheaders();
    }
}

public interface servletresponse {
    string getcharacterencoding();

    string getcontenttype();
    
    //servletresponse有一个输出流对象,保存需要相应客户端的字节流
    servletoutputstream getoutputstream() throws ioexception;

    printwriter getwriter() throws ioexception;

    void setcharacterencoding(string var1);

    void setcontentlength(int var1);

    void setcontentlengthlong(long var1);

    void setcontenttype(string var1);

    void setbuffersize(int var1);

    int getbuffersize();

    void flushbuffer() throws ioexception;

    void resetbuffer();

    boolean iscommitted();

    void reset();

    void setlocale(locale var1);

    locale getlocale();
}

我们具体看看  ((httpmessageconverter) messageconverter).write(outputvalue, selectedmediatype, outputmessage);

 

protected void writeinternal(object obj, httpoutputmessage outputmessage) throws ioexception, httpmessagenotwritableexception {
    httpheaders headers = outputmessage.getheaders();
    //创建一个数组字节流缓冲对象
    bytearrayoutputstream outnew = new bytearrayoutputstream();
    //将obj对象转换成json并写入bytearrayoutputstream中
    int len = json.writejsonstring(outnew, this.fastjsonconfig.getcharset(), obj, this.fastjsonconfig.getserializeconfig(), this.fastjsonconfig.getserializefilters(), this.fastjsonconfig.getdateformat(), json.default_generate_feature, this.fastjsonconfig.getserializerfeatures());
    headers.setcontentlength((long)len);
    //获取servletresponse的输出流对象    
    outputstream out = outputmessage.getbody();
    //将转换后的outnew写入servletresponse的输出流对象,这样就可以给客户端响应数据了
    outnew.writeto(out);
    outnew.close();
}

public outputstream getbody() throws ioexception {
    this.bodyused = true;
    this.writeheaders();
    //获取servletresponse的输出流对象    
    //servletoutputstream getoutputstream() throws ioexception;
    return this.servletresponse.getoutputstream();
}

Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析

最后我们看看json是怎么将obj对象转换成json对象的流

Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析

就是做一些循环拼接。

 至此我们基本走完了一个http请求报文经过处理后到http响应报文的转换过程。现在你可能有个疑惑,springmvc我们都是开箱即用,这些参数解析器和返回值处理器在哪里定义的呢?在核心的handleradapter实现类requestmappinghandleradapter的初始化方法中定义的。

而在requestmappinghandleradapter构造时,也同时初始化了众多的httpmessageconverter,以支持多样的转换需求。

webmvcconfigurationsupport.java

protected final void adddefaulthttpmessageconverters(list<httpmessageconverter<?>> messageconverters) {
    stringhttpmessageconverter stringconverter = new stringhttpmessageconverter();
    stringconverter.setwriteacceptcharset(false);

    messageconverters.add(new bytearrayhttpmessageconverter());
    messageconverters.add(stringconverter);
    messageconverters.add(new resourcehttpmessageconverter());
    messageconverters.add(new sourcehttpmessageconverter<source>());
    messageconverters.add(new allencompassingformhttpmessageconverter());

    if (romepresent) {
        messageconverters.add(new atomfeedhttpmessageconverter());
        messageconverters.add(new rsschannelhttpmessageconverter());
    }

    if (jackson2xmlpresent) {
        objectmapper objectmapper = jackson2objectmapperbuilder.xml().applicationcontext(this.applicationcontext).build();
        messageconverters.add(new mappingjackson2xmlhttpmessageconverter(objectmapper));
    }
    else if (jaxb2present) {
        messageconverters.add(new jaxb2rootelementhttpmessageconverter());
    }

    if (jackson2present) {
        objectmapper objectmapper = jackson2objectmapperbuilder.json().applicationcontext(this.applicationcontext).build();
        messageconverters.add(new mappingjackson2httpmessageconverter(objectmapper));
    }
    else if (gsonpresent) {
        messageconverters.add(new gsonhttpmessageconverter());
    }
}

对于json或xml的转换方式,只要引入了jackson的依赖,即可自动发现,并注册相关的转换器。

<dependency>
    <groupid>com.fasterxml.jackson.core</groupid>
    <artifactid>jackson-databind</artifactid>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupid>com.fasterxml.jackson.dataformat</groupid>
    <artifactid>jackson-dataformat-xml</artifactid>
    <version>2.9.0</version>
</dependency>

现在明白了springmvc做到了灵活又便捷的使用方式,其实在内部是做了大量的准备工作的。