SpringMVC笔记--8类型转换HttpMessageConverter

在SpringMVC框架中,需要收集用户请求参数,并将请求参数传递给应用的控制器组件。此时存在一个问题,所有的请求参数类型只能是字符串数据类型,但java是强类型语言,所以SpringMVC必须将这些字符串请求参数转换成响应的数据类型。

如一个jsp表单中有商品名称string,商品价格double,商品数量int。

如果自己写HttpServlet的话,需要把请求参数中的参数用Double.parseDouble(变量)来进行类型转换。

这里写图片描述

Converter

为什么页面上输入”12”,可以赋值给Handler方法对应的参数?
这是因为框架内部帮我们做了类型转换的工作。将String转换成int
但默认类型转换器并不是可以将用户提交的String,转换为用户需要的所有类型。此时 ,就需要自定义类型转换器了

Spring MVC的Converter<S源,T目的>是一个可以类型转换的接口。

HttpMessageConverter接口中的方法

HttpMessageConverter<\T>接口中定义了以下几个方法:

boolean canRead(Class<?> clazz,MediaType mediaType )该方法指定转换器可以读取的对象类型,即转换器可将请求信息转换为clazz 类型的对象,同时指定支持的MIME类型(text/html、application/json)

boolean canWrite(Class<?> clazz,MediaType nediaType) 该方法指定类型转换器可以将clazz类型的对象写到相应流当中,响应流支持的媒体类型在mediaType中定义

List<MediaType> getSupportedMediaTypes()该方法返回当前转换器支持的媒体类型

T read(Class<? extends T> clazz,HttpInputMessage inputMessage )该方法将请求信息流转换为T类型的对象

void write(T t,MediaType contentType,HttpOutputMessage outputMessage)该方法将T类型的对象写到响应流当中,同时指定响应的媒体类型为contentType。

内置类型转换器

Spring为HttpMessageConverter<T>提供了众多的实现类,他们组成了一个功能强大、用途广泛的HttpMessageConverter<T>家族。

SpringMVC中,对于常用的数据类型,开发者无需创建自己的类型转换器,因为SpringMVC框架有很多内置的类型转换器完成常用的类型转换。

  • 标量转换器:

StringToBooleanConverter

String、Object、Number、Character、Properties、Locale、Enum

  • 集合、数据相关转换器

ArrayToCollectionConverter

Array、Object、Collection、Map、String

类型转换是在视图与控制器相互传递数据时发生的。SpringMVC框架对于基本类型如int,double,long,float。boolean,char等,已经做好了基本类型转换。所以在形参时直接写正确类型即可。但请求参数输入值与接收参数类型要兼容,否则报400错误。

img

HttpMessageConverter顾名思义,它负责将请求信息转换为一个对象,或者将对象输出为响应信息。前面说过,当请求映射到具体的处理方法后,DispatcherServlet调用HandlerAdapter来封装并执行处理方法。DispatcherServlet默认已经安装了AnnotationMethodHandlerAdapter作为HandlerAdapter的组件实现类,HttpMessageConverter即由AnnotationMethodHandlerAdapter使用,将请求信息转换为对象,或者将对象转换为响应信息。

AnnotationMethodHandlerAdapter默认已经装配了一下的HttpMessageConverter

StringHttpMessageConverter
ByteArrayHttpMessageConverter
SourceHttpMessageConverter
AllEncompassingFormHttpMessageConverter

如果需要装配其他类型的HttpMessageConverter,则可以在Spring的web容器上下文中自定义一个RequestMappingHandlerAdapter:

<!-- 定义一个RequestMappingHandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" p:messageConverters-ref="messageConverters"/>
<!-- HttpMessageConverter列表 -->
<util:list id="messageConverters">
    <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
    <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
    <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
    <bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>
</util:list>

如果在SpringWeb容器中显示定义了一个RequestMappingHandlerAdapter,则SpringMVC将使用它覆盖默认的RequestMappingHandlerAdapter。

如何使用HttpMessageConverter将请求信息转换并绑定到处理方法的参数中呢?SpringMVC提供了两种途径

[1] 使用@RequestBody/@ResponseBody对方法进行标注。

[2] 使用HttpEntity作为处理方法的参数或返回值。

内置类型转换器的例子

@Controller
public class UserController{
    /*
    我们已经为AnnotationMethodHandlerAdapter注册了若干个HttpMessageConverter。handle1()方法的requestBody入参标注了@RequestBody注解,Spring MVC将根据requestBody的类型查找匹配的HttpMessageConverter,由于StringHttpMessageConverter的泛型对应String,所以StringHttpMessageConverter将被Spring MVC选中,用它将请求体信息进行转换并将结果绑定到requestBody入参上。
    */
    @RequestMapping(value="/haha1")
    public String handle1(@RequestBody String requestBody){
        System.out.println(requestBody);
        return "success";
    }

    /*
    handle2()方法拥有一个@ResponseBody注解,由于方法的返回值为bytes[],Spring MVC根据类型匹配的查找将根据规则使用ByteArrayHttpMessageConverter对返回值进行处理:即将图片数据流输出到客户端。
    */
    @ResponseBody
    @RequestMapping(value="/haha2/{imageId}")
    public bytes[] handle2(@PathVariable("imageId") String imageId) throws IOException{
        Resource res = new ClassPathResource("/image.jpg");
        bytes[] fileData = FileCopyUtils.copyToByteArray(res.getInputStream());
        return fileData;//将图片数据流输出到客户端
    }
}

自定义类型转换器

比如表单提交的数据是“商品价值,商品数据了,商品名称”,我们希望它以new Goods的形式传入,属性自动赋值。

这个过程需要做以下5件事

  • 创建实体类
  • 创建控制器类
  • 创建自定义类型转换器
  • 注册类型转换器
  • 创建相关视图

1、创建实体类

GoodsModel类包含“商品价值,商品数据了,商品名称”

2、创建控制器类

主要方法是

@RequestMapping("")
public String myConverter(@RequestParams("goods" GoodsModel gm,Model model)){
    model.addAttribute("goods",gm);
    return "showGoods";
}

3、创建自定义类型转换器

实现Converter<S,T>接口,重写其中的convert()方法,返回值为转换后的类型。

public class GoodsConverter implements Converter<String,GoodsModel>{
    @Override
    public GoodsModel convert(String Source){
        GoodsModel goods = new GoodsModel();
    }
}
<!--开启mvc注解驱动  -->
<mvc:annotation-driven conversion-service="conversionService" />
<!-- 注册自定义的类型转换器 -->
<bean id="conversionService"  
      class="org.springframework.context.support.ConversionServiceFactoryBean">  
    <property name="converters">  
        <list>  
            <bean class="自己写的转换器全类名com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>  
            <bean class="com.tiantian.blog.web.converter.StringToEnumConverterFactory"/> 
        </list>  
    </property>  
</bean>  

@ResponseBody

作用:

该注解用于将Controller的方法返回的对象,根据HTTP Request Header的Accept的内容,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。

使用时机:

返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;

配置返回JSON和XML数据

下载的原理:

将服务器端的文件 以流的形式写到客户端

ResponseEntity:将要下载的文件数据,以及相应信息封装到ResponseEntity对象,浏览器端通过解析发送回去的响应数据,就可以进行一个下载操作。

RequestMapping("/download")
public ResponseEntity<byte[]> testDownload(HttpSession session)throws Exception{
    // 将要下载的文件读取成一个字节数组
    byte [] imgs;
    ServletContext sc = session.getServletContext();
    InputStream in = sc.getResourceAsString("image/1.jsp");
    imgs = new byte[in.available()];
    in.read(imgs);
    // 将响应数据以及一些响应头信息封装到ResponseEntity中
    /*
    参数:
    1 发送给浏览器端的数据
    2 设置响应头
    3 设置响应码
    */
    HttpHeads headers = new HttpHeaders();
    headers.add("Content-Disposition","attachment;filename=1.jsp");
    HttpStatus statusCode = HttpStatus/OK;//200
    ResponseEntity<byte[]> re = new ResponseEntity<byte[]>(imgs,header,statusCode);
}

RestTemplate是Spring的模板类,在哭护短程序中可使用该类调用Web服务器端的服务,它支持REST风格的URL。此外,它像RequestMappingHandlerAdapter一样拥有一张HttpMessageConverter的注册表,RestTemplate默认已经注册了一下HttpMessageConverter:

[1] ByteArrayHttpMessageConverter。

[2] StringHttpMessageConverter。

[3] ResourceHttpMessageConverter。

[4] SourceHttpMessageConverter。

[5] AllEncompassingFormHttpMessageConverter。

所以,在默认情况下,RestTemplate就可以利用这些HttpMessageConverter对响应数据进行相应的转换处理。可通过RestTemplate的setMessageConverters(List<HttpMessage-Converter<?>> messageConverters)方法手工注册HttpMessageConverter。

和@RequestBody/@ResponseBody类似,HttpEntity不但可以访问请求和响应报文体的数据,还可以访问请求和响应报文头的数据。SpringMVC根据HttpEntity的泛型类型查找对应的HttpMessageConverter。

// 我们可以用ResponseEntity的方式完成与之前ResponseBody类似的下载功能


/*
使用HttpEntity<String>指定入参的类型,Spring MVC分析出泛型类型为String,使用StringHttpMessageConverter将请求体内容绑定到httpEntity中。返回为String代表逻辑视图名
*/
@RequestMapping(value = "/query")
public void query(HttpEntity<String> httpEntity){//HttpEntity入参
    long contentLen = httpEntity.getHeaders().getContentLength();//将请求报文体及报文头的信息绑定到httpEntity,在方法中可以对相应信息进行访问
    System.out.println(httpEntity);
    return "success"
}
 
/*
返回类型为ResponseEntity<byte[]>,Spring MVC分析出泛型类型为byte[],使用ByteArrayHttpMessageConverter输出图片数据流
*/
@RequestMapping(value = "/image")
public ResponseEntity<byte[]> image() throws IOException {
    Resource resource = new ClassPathResource("/image.jpg");
    byte[] file = FileCopyUtils.copyToByteArray(resource.getInputStream());
    // 在方法中创建HttpEntidy<byte[]>对象并返回,ByteArrayHttpMessageConverter负责将其写到响应流中
    return  new ResponseEntity<byte[]>(file, HttpStatus.OK);
}

通过上面两个方法可以得出以下几条结论:

① 当控制器处理方法使用@RequestBody/@ResponseBody或HttpEntity/ResponseEntity时,SPring MVC才使用注册的HttpMessageConverter对请求/响应消息进行处理。

② 当控制器处理方法使用@RequestBody/@ResponseBody或HttpEntity/ResponseEntity时,Spring首先根据请求头或响应头的Accept属性选择匹配的HttpMessageConverter,然后根据参数类型或反省类型的过滤得到匹配的HttpMessageConverter,如果找不到可用的HttpMessageConverter则报错。

③ @RequestBody和@ResponseBody不需要成对出现。如果方法参数使用了@RequestBody,则SpringMVC选择匹配的HttpMessageConverter将请求消息转换并绑定到该参数中。如果处理方法标注了@ResponseBody,则SpringMVC选择匹配的HttpMessageConverter将方法返回值转换并输出响应消息。

④ HttpEntity/ResponseEntity的功能和@RequestBody/@ResponseBody相似。

3 处理XML和JSON

SpringMVC提供了几个处理XML和JSON格式的请求/响应消息的HttpMessageConverter。

[1] MarshallingHttpMessageConverter:处理XML格式的请求或响应消息。

[2]Jaxb2RootElementHttpMessageConverter:同上,底层使用JAXB。

[3] MappingJackson2HttpMessageConverter:处理JSON格式的请求或响应消息。

因此,只要在Spring Web容器中为RequestMappingHandlerAdapter装配好响应的处理XML和JSON格式的请求/响应消息的HttpMessageConverter,并在交互中通过请求的Accept指定MIME类型,SpringMVC就可使服务器端的处理方法和客户端透明的通过XML或JSON格式的消息进行通信,开发者几乎无需关心通信层数据格式的问题,可以将精力集中到业务层的处理上。单就这一点而言,其他MVC框架就无法和SpringMVC相比。

首先为AnnotationMethodHandlerAdapter装配上可处理JSON的HttpMessageConverter:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list >
            <ref bean="mappingJacksonHttpMessageConverter" />
        </list>
    </property>
</bean>

<bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
    <property name="supportedMediaTypes">
        <list>
            <value>application/json; charset=UTF-8</value>
            <value>text/html; charset=UTF-8</value>
        </list>
    </property>
</bean> 

装配好处理XML和JSON的HttpMessageConverter后,我们在控制器中编写响应的方法

@RequestMapping("/product")
@ResponseBody
public List<Product> findProductList() {
    List<Product> liProduct= new ArrayList<Product>();
    for (ProductCodeEnum t : ProductCodeEnum.values())
    {
        Product product = new Product();
        product.setId(t.getCode());
        product.setText(t.getCnName());
        product.setLeaf(true);

        liProduct.add(product);
    }
    return liProduct;
}

对服务端的处理方法而言,除使用@ResponseBody/@ResponseBody或HttpEntity/ResponseEntity进行方法签名外,不需要进行进行额外的处理,借由Spring MVC中装配的HttpMessageConverter,它即拥有了处理XML及JSON的能力了

在接收到一个HTTP请求后,handle方法如何知道请求消息的格式,在处理完成后,又根据什么确定响应消息的格式呢?答案很简单:通过请求消息头的“Content-Type”及“Accept”属性确定。

Formatter

和上面一样,也是一个可以转换类型的接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值