Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析
概述
在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进行转换。流程如下:
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抽象基类中。
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方法完成转换。
我们看看传入的参数 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(); }
最后我们看看json是怎么将obj对象转换成json对象的流
就是做一些循环拼接。
至此我们基本走完了一个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做到了灵活又便捷的使用方式,其实在内部是做了大量的准备工作的。
上一篇: 洛谷 P3366 【模板】最小生成树
下一篇: Scala模式匹配(match)使用