SpringMVC 处理 JSON
由于现在我们使用 Ajax 的机会非常多,所以我们有必要来看一下 SpringMVC 是如何处理 JSON 格式的数据的。
我们先来看一个处理 JSON 的程序,再来分析它其中的原理
创建 Employee 实体类
package com.bupt.springmvc.converter.entity; import javax.validation.constraints.Past; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.format.annotation.DateTimeFormat; public class Employee { private Integer id; @NotEmpty private String lastName; @Email private String email; //生成 getter 和 setter 方法,生成带参和不带参的构造方法,重写 toString }
创建 EmployeeDao 类
package com.bupt.springmvc.converter.Dao; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.springframework.stereotype.Repository; import com.bupt.springmvc.converter.entity.Employee; @Repository public class EmployeeDao { private static Map<Integer, Employee> employees = null; static { employees = new HashMap<Integer, Employee>(); employees.put(1001, new Employee(1001, "E-AA", "[email protected]")); employees.put(1002, new Employee(1002, "E-BB", "[email protected]")); employees.put(1003, new Employee(1003, "E-CC", "[email protected]")); employees.put(1004, new Employee(1004, "E-DD", "[email protected]")); employees.put(1005, new Employee(1005, "E-EE", "[email protected]")); } public Collection<Employee> getAll() { return employees.values(); } }
配置spring配置文件 springmvc.xml
<!-- 配置自动扫描的包 --> <context:component-scan base-package="com.bupt.springmvc.converter"></context:component-scan> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"></property> <property name="suffix" value=".jsp"></property> </bean> <mvc:default-servlet-handler/> <mvc:annotation-driven></mvc:annotation-driven>
编写 index.jsp 页面
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Insert title here</title> <script type="text/javascript" src="script/jquery-1.9.1.min.js"></script> <%-- 点击链接触发弹窗,将返回信息显示在弹窗上 --%><script type="text/javascript"> $(function(){ $("#testJson").click(function(){ var url = this.href; var args = {}; $.post(url, args, function(data){ for(var i = 0; i < data.length; i++) { var id = data[i].id; var lastName = data[i].lastName; alert(id + ": " + lastName); } }); return false; }); }) </script> </head> <body> <a href="testJson" id="testJson">Test JSON</a> </body> </html>
想要让 Spring 处理 JSON,我们需要几步来完成
1. 首先我们需要导入依赖包:jackson-annotations.jar;jackson-core.jar;jackson-databind.jar
2. 编写目标方法,使其返回 JSON 对应的对象或集合
3. 在方法上添加 @ResponseBody 注解(作用后面会讲)
package com.bupt.springmvc.converter.handler; import java.util.Collection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.bupt.springmvc.converter.Dao.EmployeeDao; import com.bupt.springmvc.converter.entity.Employee; @Controller public class ConverterHandler { @Autowired private EmployeeDao employeeDao; @ResponseBody @RequestMapping("/testJson") public Collection<Employee> testJson() { return employeeDao.getAll(); } }
启动 tomcat,访问 index.jsp,点击超链接,弹窗跳出。同时查看返回信息为如图所示的 JSON 格式数据
如此,我们的响应返回的数据格式就是以 JSON 格式返回的。
那这整个过程是怎么完成的呢? HttpMessageConverter<T> 这个接口是整个过程中最为重要的一个环节
HttpMessageConverter<T> 是 SpringMVC 3.0 新增的一个接口,负责将请求信息转换为一个对象(类型为T),将对象(类型为T)输出为响应信息。
HttpMessageConverter<T> 接口定义的方法:
1. Boolean canRead(Class<?> clazz, MediaType mediaType):指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对象,同时指定支持 MIME 类型(text/html, application/json等)
2. Boolean canWrite(Class<?> clazz, MediaType mediaType):指定转换器是否将clazz类型的对象写到响应流中,响应流支持的媒体类型在 MediaType 中定义
3. List<MediaType getSupportMediaType>():该转换器支持的媒体类型
4. T read(Class<? entends T> clazz, HttpInputMessage inputMessage):将请求信息流(inputMessage)转换为 T 类型的对象
5. void write(T t, MediaType contentType, HttpOutputMessage outputMeaasge):将 T 类型的对象写到响应流(outputMeaasge)中,同时指定相应的媒体类型为 contentType
它整个在请求响应中的作用可以用下图来表示
其中 HttpInputMessage 和 HttpOutputMessage 是两个继承于 HttpMessage 的接口,它们都只包含一个方法
InputStream getBody() throws IOExcpetion;
OutputStream getBody() throws IOExcpetion;
HttpMessageConverter<T> 有很多的实现类,下面列出一部分
实现类 | 功能说明 |
StringHttpMessageConverter | 将请求信息转换成字符串 |
FormHttpMessageConverter | 将表单数据读取到MultiValueMap中 |
XmlAwareFormHttpMessageConverter | 扩展于 FormHttpMessageConverter,如果部分表单属性是XML数据,可用数据转换器进行读取 |
ResourceHttpMessageConverter | 读取org.springframework.core.io.Resource对象 |
BufferedImageHttpMessageConverter | 读取BufferedImage对象 |
ByteArrayHttpMessageConverter | 读取二进制数据 |
SourceHttpMessageConverter | 读取 javax.xml.transform.Source类型的数据 |
MarshallingHttpMessageConverter | 通过Spring 的 org.springframework.xml.Marshaller 与 Unmarshaller 读取 XML 消息 |
Jaxb2RootElementHttpMessageConverter | 通过 JAXB2 读写 XML 消息,将请求信息转换到标准XmlRootElement 和 XxmlType 直接的类中 |
MappingJacksonHttpMessageConverter | 利用 Jackson 开源包 ObjectMapper 读写 JSON 数据 |
RssChannelHttpMessageConverter | 能够读写 RSS 种子消息 |
那么 SpringMVC 搭载了哪些实现类呢?
DispatcherServlet 默认装配 RequestMappingHandlerAdapter,而 RequestMappingHandlerAdapter 默认装配如下 HttpMessageConverter
加入 jackson 开源包后,RequestMappingHandlerAdapter 装配的 HttpMessageConverter 如下,处理JSON的Converter就绑定到我们的 SpringMVC 中去了
HttpMessageConverter<T> 到底干了些什么?以及如何确定使用哪种 HttpMessageConverter<T> 的实现类呢
使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:
1. 使用 @RequestBody/@ResponseBody 对处理方法进行标注
2. 使用 HttpEntity<T>/ResponseEntity<T> 作为处理方法的入参或返回值
当控制器处理方法使用到 @RequestBody/@ResponseBody 或 HttpEntity<T>/ResponseEntity<T> 时,Spring 首先根据请求头或响应的 Accept 属性选择匹配的 HttpMessageConverter,进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter,若找不到可用的 HttpMessageConverter 将报错
其中,@RequestBody/@ReponseBody 不需要成对出现
如何指定返回类型是 JSON
具体可以表述为,当使用 @ReponseBody 修饰时是根据其方法返回值确定具体对应的 converter
//返回值为 String 类型,使用的 converter 为 ByteArrayHttpMessageConverter @ResponseBody @RequestMapping("/handler") public byte[] handler()
当使用 @RequestBody 修饰时根据的是 入参类型
//入参类型为 String 类型,使用的 converter 为 StringHttpMessageConverter @RequestMapping(value="/handler", method=RequestMethod.POST) public String handler(@RequestBody String requestBody)
通过一个文件上传的例子来看一下 @RequestBody/@ReponseBody 的使用
在处理方法类中添加处理方法
@ResponseBody @RequestMapping("/testHttpMessageConverter") public String testHttpMessageConverter(@RequestBody String body) { System.out.println(body); return "Time: " + new Date(); }
在 index.jsp 添加文件上传表单
<form action="testHttpMessageConverter" method="POST" enctype="multipart/form-data"> File: <input type="file" name="file"/> Desc: <input type="text" name="desc"/><br> <input type="submit" value="submit"/> </form>
我上传的.txt文件中只有一句话:Write the code Change the world,DESC填写的内容为 ABCD
启动服务器,点击上传文件,控制台和页面输出内容如图
注:此方法不能用于真正的文件上传,因为它将表单内容和文件内容都混为 String 类型,解析时不能区分两者。SpringMVC 中使用的文件上传会在文章最后介绍。
通过一个文件下载的例子来看一下 ResponseEntity<T> 如何使用
WebContext下新建文件夹,内置一个名为 abc.txt 的文件
方法类中新增方法
@RequestMapping("/testResponseEntity") public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException { byte[] body = null; ServletContext servletContext = session.getServletContext(); InputStream in = servletContext.getResourceAsStream("/files/abc.txt"); body = new byte[in.available()]; in.read(body); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment;filename=abc.txt"); HttpStatus statusCode = HttpStatus.OK; ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(body, headers, statusCode); return response; }
index.jsp 新增请求连接
<a href="testResponseEntity">Test ResponseEntity</a>
启动服务器点击链接时,即会弹出下载提示框
文件上传
SpringMVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResolver。
SpringMVC 上下文中默认没有装配 MultipartResolver,因为默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需要在上下文中配置 MultipartResolver。配置之前我们需要导入 commons-fileupload.jar 和 commons-io.jar 两个jar包
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 请求编码格式,默认为ISO-8859-1 --> <property name="defaultEncoding" value="utf-8"/> <!-- 上传文件大小上限,单位为字节(1M) --> <property name="maxUploadSize" value="1024000"/> <!-- 上传文件的临时路径 --> <property name="uploadTempDir" value="upload/temp"/> </bean>
defaultEncoding 必须和用户 JSP 的 pageEncoding 属性一致,以便正确读取表单的内容。iploadTempDir 是文件上传过程中使用的临时目录,文件上传完成后,临时目录中的临时文件会被自动清除。
SpringMVC 会将上传的文件绑定到 MultipartFile 对象中。MultipartFile 提供了获取上传文件内容、文件名等内容,通过其 transferTo(File dest) 方法还可将文件存储到硬件上
在方法处理器类中编写相应的处理方法
@RequestMapping("/testFileUpload") public String testFileUpload(@RequestParam("desc") String desc, @RequestParam("file") MultipartFile file) throws IOException { System.out.println("desc: " + desc); System.out.println("OriginalFilename: " + file.getOriginalFilename()); System.out.println("InputStream: " + file.getInputStream()); file.transferTo(new File("D:/abcd.txt")); return "success"; }
编写 index.jsp 和 success.jsp 页面
<form action="testFileUpload" method="post" enctype="multipart/form-data"> File: <input type="file" name="file"/><br> Desc: <input type="text" name="desc"><br> <input type="submit" value="submit"> </form>
<body> <h4>SUCCESS PAGE</h4> </body>
选择上传的文件为 test.txt,DESC 填写内容为 ABCD,控制台打印的结果为
查看D盘下的找到dbcd.txt,可以看到其内容与上传文件内容一致。
MultipartFile 中的一些方法说明:
byte[] getBytes():获取文件数据
String getContentType():获取文件 MIME 类型,如image/pjpeg、text/plain等
InputStream getInputStream():获取文件流
String getName():获取表单中文件组件的名字
String getOriginalFileName():获取上传文件的原名
long getSize():获取文件的字节大小,单位为 byte
boolean isEmpty():是否有上传的文件
void transferTo(File dest):可以使用该方法将上传文件保存到一个目标文件中