文档章节

Spring MVC HTTP Message Conversion

秋风醉了
 秋风醉了
发布于 2015/07/19 02:30
字数 1799
阅读 191
收藏 3

Spring MVC HTTP Message Conversion

在spring mvc中,一个http请求和响应流程如下图所示:

其中 HttpMessageConvert 扮演了 http请求消息和响应消息进行转换和角色。

这里先说一下对于 HTTP 请求和响应的一般抽象。

 

HTTP 请求和响应的封装抽象

我们知道,在servlet标准中,可以用javax.servlet.ServletRequest 接口中的以下方法:

public ServletInputStream getInputStream() throws IOException;

来得到一个ServletInputStream。这个ServletInputStream中,可以读取到一个原始请求报文的所有内容。

同样的,在javax.servlet.ServletResponse 接口中,可以用以下方法:

public ServletOutputStream getOutputStream() throws IOException;

来得到一个ServletOutputStream,这个ServletOutputSteam,继承自java中的OutputStream,可以让你输出Http的响应报文内容。

让我们尝试着像SpringMVC的设计者一样来思考一下。我们知道,Http请求和响应报文本质上都是一串字符串,当请求报文来到java世界,它会被封装成为一个ServletInputStream的输入流,供我们读取报文。响应报文则是通过一个ServletOutputStream的输出流,来输出响应报文。

 

而在 SpringMVC 中则提供了以下两个类来进行更高层的对 http 请求和响应的封装抽象。

HttpInputMessage

这个类是SpringMVC内部对一次Http请求报文的抽象 ,在HttpMessageConverter的read()方法中,有一个HttpInputMessage的形参,它正是SpringMVC的消息转换器所作用的受体“请求消息”的内部抽象,消息转换器从“请求消息”中按照规则提取消息,转换为方法形参中声明的对象。

package org.springframework.http;
import java.io.IOException;
import java.io.InputStream;
public interface HttpInputMessage extends HttpMessage {
    InputStream getBody() throws IOException;
}

HttpOutputMessage

这个类是SpringMVC内部对一次Http响应报文的抽象  ,在HttpMessageConverter的write()方法中,有一个HttpOutputMessage的形参,它正是SpringMVC的消息转换器所作用的受体“响应消息”的内部抽象,消息转换器将“响应消息”按照一定的规则写到响应报文中。

package org.springframework.http;
import java.io.IOException;
import java.io.OutputStream;
public interface HttpOutputMessage extends HttpMessage {
    OutputStream getBody() throws IOException;
}

现在再回来说一下 HttpMessageConvert 在SpringMVC的http请求和响应流程中所起到的作用。

 

HttpMessageConvert-HTTP消息转换器

HttpMessageConverter接口描述:

public interface HttpMessageConverter<T> {

    // Indicate whether the given class and media type can be read by this converter.
    boolean canRead(Class<?> clazz, MediaType mediaType);

    // Indicate whether the given class and media type can be written by this converter.
    boolean canWrite(Class<?> clazz, MediaType mediaType);

    // Return the list of MediaType objects supported by this converter.
    List<MediaType> getSupportedMediaTypes();

    // Read an object of the given type from the given input message, and returns it.
    T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;

    // Write an given object to the given output message.
    void write(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;

}

那么springmvc是如何实例化HttpMessageConvert的呢?这就是<mvc:annotation-driven/>标签的作用了。

我们一般要在 spirngmvc的配置文件中加这个标签,这个标签的实现类就是

org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser

通过注释文档你可以看到它的主要作用是:

1.注入HandlerMapping(用来处理请求映射的。其中第一个是处理@RequestMapping注解的。第二个会将controller类的名字映射为请求url。)

* <p>This class registers the following {@link HandlerMapping}s:</p>
* <ul>
* <li>{@link RequestMappingHandlerMapping}
* ordered at 0 for mapping requests to annotated controller methods.
* <li>{@link BeanNameUrlHandlerMapping}
* ordered at 2 to map URL paths to controller bean names.
* </ul>

2.注入HandlerAdapter(RequestMappingHandlerAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter三个是用来处理请求的。具体点说就是确定调用哪个controller的哪个方法来处理当前请求。第一个处理@Controller注解的处理器,支持自定义方法参数和返回值(很酷)。第二个是处理继承HttpRequestHandler的处理器。第三个处理继承自Controller接口的处理器。)

* <p>This class registers the following {@link HandlerAdapter}s:
* <ul>
* <li>{@link RequestMappingHandlerAdapter}
* for processing requests with annotated controller methods.
* <li>{@link HttpRequestHandlerAdapter}
* for processing requests with {@link HttpRequestHandler}s.
* <li>{@link SimpleControllerHandlerAdapter}
* for processing requests with interface-based {@link Controller}s.
* </ul>

3.注入HandlerExceptionResolver(处理异常)

* <p>This class registers the following {@link HandlerExceptionResolver}s:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions
* through @{@link ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated
* with @{@link ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring
* exception types
* </ul>

4.注入AntPathMatcher和UrlPathHelper

* <p>This class registers an {@link org.springframework.util.AntPathMatcher}
* and a {@link org.springframework.web.util.UrlPathHelper} to be used by:
* <ul>
* <li>the {@link RequestMappingHandlerMapping},
* <li>the {@link HandlerMapping} for ViewControllers
* <li>and the {@link HandlerMapping} for serving resources
* </ul>
* Note that those beans can be configured by using the {@code path-matching} MVC namespace element.

更详细的可以读api doc。

好了,那么HttpMessageConvert又是从哪里注入的呢?别急,通过读AnnotationDrivenBeanDefinitionParser类的代码,发现messageConverters是在注入RequestMappingHandlerAdapter时实例化的,具体的代码如下,

RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
addResponseBodyAdvice(handlerAdapterDef);

handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);

我们就来看看这个标签默认为我们实例化了哪些 HttpMessageConvert。代码如下,

private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
    Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
    ManagedList<? super Object> messageConverters = new ManagedList<Object>();
    if (convertersElement != null) {
        messageConverters.setSource(source);
        for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
            Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
            messageConverters.add(object);
        }
    }

    if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
        messageConverters.setSource(source);
        messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));

        RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
        stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
        messageConverters.add(stringConverterDef);

        messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
        messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
        messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));

        if (romePresent) {
            messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
            messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
        }

        if (jackson2XmlPresent) {
            RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);
            GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
            jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);
            jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
            messageConverters.add(jacksonConverterDef);
        }
        else if (jaxb2Present) {
            messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
        }

        if (jackson2Present) {
            RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
            GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
            jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
            messageConverters.add(jacksonConverterDef);
        }
        else if (gsonPresent) {
            messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));
        }
    }
    return messageConverters;
}

通过上面的代码,可以很清楚的看到默认实例化的HttpMessageConvert如下:

  • ByteArrayHttpMessageConverter

  • StringHttpMessageConverter

  • ResourceHttpMessageConverter

  • SourceHttpMessageConverter

  • AllEncompassingFormHttpMessageConverter

如果相应的消息转换类库在classpath下并且能够被加载(classload),那么以下convert也会默认实例化,

  • AtomFeedHttpMessageConverter

  • RssChannelHttpMessageConverter

  • MappingJackson2XmlHttpMessageConverter

  • Jaxb2RootElementHttpMessageConverter

  • MappingJackson2HttpMessageConverter

  • GsonHttpMessageConverter

哈哈,齐全了!有这些convert转换消息还是不够的。它是如何选择一个具体的convert来转换消息的呢?

HttpMessageConverter接口的定义出现了成对的canRead(),read()和canWrite(),write()方法,MediaType是对请求的MediaType属性的封装。

举个例子,当我们声明了下面这个处理方法。

@RequestMapping(value="/string", method=RequestMethod.POST)
public @ResponseBody String readString(@RequestBody String string) {
    return "Read string '" + string + "'";
}

在SpringMVC进入readString方法前,会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中,具体来说是使用了StringHttpMessageConverter类,它的canRead()方法返回true,然后它的read()方法会从请求中读出请求参数,绑定到readString()方法的string变量中。

当SpringMVC执行readString方法后,由于返回值标识了@ResponseBody,SpringMVC将使用StringHttpMessageConverter的write()方法,将结果作为String值写入响应报文,当然,此时canWrite()方法返回true。

这个转换过程就是本文一开始贴的图片。将上述过程集中描述的一个类是org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor,这个类同时实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两个接口。前者是将请求报文绑定到处理方法形参的策略接口,后者则是对处理方法返回值进行处理的策略接口。两个接口的源码如下:

package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter parameter);
    Object resolveArgument(MethodParameter parameter,
                           ModelAndViewContainer mavContainer,
                           NativeWebRequest webRequest,
                           WebDataBinderFactory binderFactory) throws Exception;
}
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
public interface HandlerMethodReturnValueHandler {
    boolean supportsReturnType(MethodParameter returnType);
    void handleReturnValue(Object returnValue,
                           MethodParameter returnType,
                           ModelAndViewContainer mavContainer,
                           NativeWebRequest webRequest) throws Exception;
}

RequestResponseBodyMethodProcessor这个类,同时充当了方法参数解析和返回值处理两种角色。

参考引用:http://my.oschina.net/lichhao/blog/172562

http://my.oschina.net/HeliosFly/blog/205343

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html

===================END===================

© 著作权归作者所有

共有 人打赏支持
秋风醉了
粉丝 246
博文 543
码字总数 412294
作品 0
朝阳
程序员
私信 提问
关于Spring MVC 3.1.x中如何替换数据Converter的问题

参考文献(15-09-20补充): + http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#rest-message-conversion+ http://docs.spring.io/spring/docs/current/sp......

IncRediblE
2014/07/25
0
0
SpringMVC Json数据 转换成Object

WEB页面提交一个请求,想把一个Json数据传到controller里面。贴下大概代码 WEB页面Ajax请求 $.ajax({ url:"index/color/update.json", data:arr[0], type:"post", dataType:"json", content......

月沉海雾
2013/03/13
7.4K
4
Spring3.0.4 学习与问题总结系列 - 2 MVC

记得在3年前使用的是 Struct2 与Spring结合的框架开发。当时是用Structs2来进行MVC管理,使用Spring对BO与DAO的Bean管理。还是一个新人的我,懵懂的做了3年。 因为很长时间已经没有接触过这样...

李长春
2011/09/15
0
0
spring如何在xml里配置Calendar,Date

文章地址:http://blog.csdn.net/hengyunabc/article/details/14107963 在Spring MVC里可以通过message converter机制来对数据进行格式化,但是在普通的Spring xml配置里就无能为力了。 在网...

横云断岭
2013/11/03
0
0
SpringMVC Clob字段如何接收

普通的Spring JPA 表单增删改查。 javabean: 比如我有n个不同类型的字段。 controller层代码: 我通过这种方法来将页面form值直接通过@ModelAttribuite 从pagecjjj中取 String的参数是没有问...

程序员Joe
2014/10/15
1K
2

没有更多内容

加载失败,请刷新页面

加载更多

租房软件隐私保护如同虚设

近日,苏州市民赵先生向江苏新闻广播新闻热线025-84658888反映,他在“安居客”手机应用软件上浏览二手房信息,并且使用该软件自动生成的虚拟号码向当地一家中介公司进行咨询。可电话刚挂不久...

linux-tao
今天
1
0
分布式项目(五)iot-pgsql

书接上回,在Mapping server中,我们已经把数据都整理好了,现在利用postgresql存储历史数据。 iot-pgsql 构建iot-pgsql模块,这里我们写数据库为了性能考虑不在使用mybatis,换成spring jd...

lelinked
今天
4
0
一文分析java基础面试题中易出错考点

前言 这篇文章主要针对的是笔试题中出现的通过查看代码执行结果选择正确答案题材。 正式进入题目内容: 1、(单选题)下面代码的输出结果是什么? public class Base { private Strin...

一看就喷亏的小猿
今天
2
0
cocoapods 用法

cocoapods install pod install 更新本地已经install的仓库 更新所有的仓库 pod update --verbose --no-repo-update 更新制定的仓库 pod update ** --verbose --no-repo-update...

HOrange
今天
3
0
linux下socket编程实现一个服务器连接多个客户端

使用socekt通信一般步骤 1)服务器端:socker()建立套接字,绑定(bind)并监听(listen),用accept()等待客户端连接。 2)客户端:socker()建立套接字,连接(connect)服务器,连接上后...

shzwork
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部