SpringMVC 處理 JSON
由於現在我們使用 Ajax 的機會非常多,所以我們有必要來看一下 SpringMVC 是如何處理 JSON 格式的數據的。
我們先來看一個處理 JSON 的程序,再來分析它其中的原理
創建 Employee 實體類
1 package com.bupt.springmvc.converter.entity; 2 3 import javax.validation.constraints.Past; 4 import org.hibernate.validator.constraints.Email; 5 import org.hibernate.validator.constraints.NotEmpty; 6 import org.springframework.format.annotation.DateTimeFormat; 7 8 public class Employee 9 { 10 private Integer id; 11 12 @NotEmpty 13 private String lastName; 14 15 @Email 16 private String email; 17 18 //生成 getter 和 setter 方法,生成帶參和不帶參的構造方法,重寫 toString 19 }
創建 EmployeeDao 類
1 package com.bupt.springmvc.converter.Dao; 2 3 import java.util.Collection; 4 import java.util.HashMap; 5 import java.util.Map; 6 import org.springframework.stereotype.Repository; 7 import com.bupt.springmvc.converter.entity.Employee; 8 9 @Repository 10 public class EmployeeDao 11 { 12 private static Map<Integer, Employee> employees = null; 13 14 static 15 { 16 employees = new HashMap<Integer, Employee>(); 17 18 employees.put(1001, new Employee(1001, "E-AA", "aa@163.com")); 19 employees.put(1002, new Employee(1002, "E-BB", "bb@163.com")); 20 employees.put(1003, new Employee(1003, "E-CC", "cc@163.com")); 21 employees.put(1004, new Employee(1004, "E-DD", "dd@163.com")); 22 employees.put(1005, new Employee(1005, "E-EE", "ee@163.com")); 23 } 24 25 public Collection<Employee> getAll() 26 { 27 return employees.values(); 28 } 29 }
配置spring配置文件 springmvc.xml
1 <!-- 配置自動掃描的包 --> 2 <context:component-scan base-package="com.bupt.springmvc.converter"></context:component-scan> 3 4 <!-- 配置視圖解析器 --> 5 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 6 <property name="prefix" value="/WEB-INF/views/"></property> 7 <property name="suffix" value=".jsp"></property> 8 </bean> 9 10 <mvc:default-servlet-handler/> 11 12 <mvc:annotation-driven></mvc:annotation-driven>
編寫 index.jsp 頁面
1 <%@ page language="java" contentType="text/html; charset=ISO-8859-1" 2 pageEncoding="ISO-8859-1"%> 3 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 4 <html> 5 <head> 6 <title>Insert title here</title> 7 <script type="text/javascript" src="script/jquery-1.9.1.min.js"></script> 8 <%-- 點擊鏈接觸發彈窗,將返回信息顯示在彈窗上 --%> 9 <script type="text/javascript"> 10 $(function(){ 11 $("#testJson").click(function(){ 12 var url = this.href; 13 var args = {}; 14 $.post(url, args, function(data){ 15 for(var i = 0; i < data.length; i++) 16 { 17 var id = data[i].id; 18 var lastName = data[i].lastName; 19 20 alert(id + ": " + lastName); 21 } 22 }); 23 return false; 24 }); 25 }) 26 </script> 27 </head> 28 <body> 29 <a href="testJson" id="testJson">Test JSON</a> 30 </body> 31 </html>
想要讓 Spring 處理 JSON,我們需要幾步來完成
1. 首先我們需要導入依賴包:jackson-annotations.jar;jackson-core.jar;jackson-databind.jar
2. 編寫目標方法,使其返回 JSON 對應的對象或集合
3. 在方法上添加 @ResponseBody 注解(作用后面會講)
1 package com.bupt.springmvc.converter.handler; 2 3 import java.util.Collection; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.stereotype.Controller; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.ResponseBody; 8 import com.bupt.springmvc.converter.Dao.EmployeeDao; 9 import com.bupt.springmvc.converter.entity.Employee; 10 11 @Controller 12 public class ConverterHandler 13 { 14 @Autowired 15 private EmployeeDao employeeDao; 16 17 @ResponseBody 18 @RequestMapping("/testJson") 19 public Collection<Employee> testJson() 20 { 21 return employeeDao.getAll(); 22 } 23 }
啟動 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 不需要成對出現
@Responsebody與@RequestBody
@Responsebody表示該方法的返回結果直接寫入HTTP response body中 一般在異步獲取數據時使用,在使用@RequestMapping后,返回值通常解析為跳轉路徑, 加上@Responsebody后返回結果不會被解析為跳轉路徑,而是直接寫入HTTP response body中。 比如異步獲取json數據,加上@Responsebody后,會直接返回json數據。
@RequestBody將HTTP請求正文插入方法中,使用適合的HttpMessageConverter將請求體寫入某個對象。
如何指定返回類型是 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):可以使用該方法將上傳文件保存到一個目標文件中