在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错误。
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
和上面一样,也是一个可以转换类型的接口。