JAXB 深入顯出 - JAXB 教程 Spring Boot返回 XML
摘要: JAXB 作為JDK的一部分,能便捷地將Java物件與XML進行相互轉換,本教程從實際案例出發來講解JAXB 2 的那些事兒。完整版目錄
前情回顧
上一節,我使用 Spring Boot 搭建的工程,能夠正確地返回JSON 資料,內容和JAXB沒有關係,主要是為這一節打基礎。
使用 JAXB 返回 XML
新增 MediaType
相對之前返回 json 格式資料,需要新增 MediaType 指定返回型別。
@GetMapping(value="/id/xml", produces=MediaType.APPLICATION_XML_VALUE)
public Student findById2(String id) {
return studentService.findById(id);
}
訪問 http://localhost:8080/student/id/xml?id=11
。
結果並沒有得到想要的資料,而是返回了錯誤頁面。
檢視Eclipse控制檯,並沒有任何錯誤輸出,說明程式碼層面沒有出問題。
檢視頁面控制檯,發現錯誤碼是 406,並且 Accept 已經是 application/xml
。
深入 406 錯誤
Spring 使用HttpMessageConverter<T>
將請求資訊轉換為一個物件(型別為 T),將物件(型別為 T)輸出為響應資訊。
並且HttpMessageConveter
- ByteArrayHttpMessageConverter
- String HttpMessageConverter
- ResourceHttpMessageConverter
- SourceHttpMessageConverter
- AllEncompassingFormHttpMessageConverter
- Jaxb2RootElementHttpMessageConverter
而對於訊息轉換器的呼叫,都是在RequestResponseBodyMethodProcessor
類中完成的。該類會根據註解中指定返回型別去找對應的轉換器,如果找不到,就丟擲HttpMediaTypeNotAcceptableException
原來Spring已經實現了Jaxb的轉換方法,那為什麼會丟擲異常呢?回想一下之前使用Jaxb的必要步驟,就知道此刻還忘記了一個重要流程。。。
JAXB 註解 @XmlRootElement
趕緊加上這個必須的註解,在需要返回的物件上加 @XmlRootElement
。因為Jaxb是JDK自帶的實現,不需要加入任何依賴 jar 包。
@XmlRootElement
public class Student {
...
}
此時,再次訪問 http://localhost:8080/student/id/xml?id=11
發現正確返回了結果。
<student>
<age>23</age>
<id>11</id>
<name>Tom</name>
</student>
處理 List 資料
失敗的實驗
接下來,照葫蘆畫瓢,新加一個方法,和返回 json 資料時一樣,只是添加了返回值格式為 XML 。
@GetMapping(value="/list/xml", produces=MediaType.APPLICATION_XML_VALUE)
public List<Student> findAll2(){
return studentService.findAll();
}
趕緊訪問以下:http://localhost:8080/student/list/xml
又給了一個 406。明明已經加了註解,怎麼還是不行呢?再次回想之前的章節,原來 JAXB 不支援 List 資料型別,看來需要對現有程式碼改造了。
小小的改造
首先,定義一個 Students
類,其中只有一個屬性,就是List<Student> student
,List
中存放Student
的資料,新增setter/getter方法,並且新增JAXB註解@XmlRootElement
.
@XmlRootElement
public class Students {
List<Student> student;
public List<Student> getStudent() {
return student;
}
public void setStudent(List<Student> student) {
this.student = student;
}
}
對於Controller也需要適當改造,需要的返回值不是List<Student>
了,而是Students
:
@GetMapping(value="/students/xml", produces=MediaType.APPLICATION_XML_VALUE)
public Students findAll3(){
Students students = new Students();
students.setStudent(studentService.findAll());
return students;
}
檢視一下資料:http://localhost:8080/student/students/xml
<students>
<student>
<age>23</age>
<id>11</id>
<name>Tom</name>
</student>
<student>
<age>25</age>
<id>12</id>
<name>Jerry</name>
</student>
<student>
<age>32</age>
<id>13</id>
<name>David</name>
</student>
<student>
<age>41</age>
<id>14</id>
<name>Jack</name>
</student>
</students>
總算達到了所需的效果,只是過程有點艱辛。
為了返回XML資料,程式碼可是比返回json多了許多,有什麼方式可以少寫點程式碼呢?當然有。只是這次需要新增一個 jar 包。
使用 jackson 返回 XML
新增依賴
加入 jackson 的依賴,讓 Spring 使用 jackson 來處理 XML 請求。
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
新增完這個依賴以後,Spring會使用另外的一個轉換器MappingJackson2HttpMessageConverter
來處理。
返回單個物件
新增一個返回Student
的方法:
@GetMapping(value="/id/jackson/xml", produces=MediaType.APPLICATION_XML_VALUE)
public Student findById3(String id) {
return studentService.findById(id);
}
首先,去掉JAXB的註解@XmlRootElement
,然後訪問:http://localhost:8080/student/id/jackson/xml?id=11
得到想要的結果:
<Student>
<id>11</id>
<name>Tom</name>
<age>23</age>
</Student>
值得注意的是,這個RootElement的名稱是返回物件的類名,而不是像JAXB一樣,返回首字母小寫的類名。
返回 List
這個方法和之前返回 json 格式的程式碼幾乎一致,只是生命了返回型別為 XML 。
@GetMapping(value="/list/jackson/xml", produces=MediaType.APPLICATION_XML_VALUE)
public List<Student> findAll4(){
return studentService.findAll();
}
訪問一下:http://localhost:8080/student/list/jackson/xml
<List>
<item>
<id>11</id>
<name>Tom</name>
<age>23</age>
</item>
<item>
<id>12</id>
<name>Jerry</name>
<age>25</age>
</item>
<item>
<id>13</id>
<name>David</name>
<age>32</age>
</item>
<item>
<id>14</id>
<name>Jack</name>
<age>41</age>
</item>
</List>
竟然驚喜地發現,沒有任何程式碼改造,就能返回想要的 XML 格式資料。比之前使用 JAXB 方式簡單許多,這也是真正開發時所選用的方式。
當然,這裡面有一些名稱有可能和需要的結果不一致,jackson
同樣提供了許多註解來滿足定製化需求。
不過此時,再次訪問一下之前返回 json 的方法:http://localhost:8080/student/id?id=11
。發現預設返回型別成為了XML,該方法返回了XML資料。
如果想要返回 json ,需要手動宣告:
@GetMapping(value="/id2", produces=MediaType.APPLICATION_JSON_VALUE)
public Student findById4(String id) {
return studentService.findById(id);
}
訪問一下:http://localhost:8080/student/id2?id=11
正確返回 json 資料。
一個請求同時支援返回 json 和 xml
觀察之前寫的程式碼:
片段1
@GetMapping(value="/id")
public Student findById(String id) {
return studentService.findById(id);
}
片段2
@GetMapping(value="/id/xml", produces=MediaType.APPLICATION_XML_VALUE)
public Student findById2(String id) {
return studentService.findById(id);
}
為了支援兩種型別的輸出,我們寫了完全相同的兩段程式碼,能不能通過一個引數的配置,來指定返回xml 還是 json 呢?嘗試一下。
修改介面卡
Java提供了一個WebMvcConfigurerAdapter
來處理與 mvc 相關的配置,我們可以通過修改這個配置來改變某些行為。
public class CustomWebMvcConfig extends WebMvcConfigurerAdapter{
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorParameter(true) // 是否支援引數化處理請求
.parameterName("format") // 引數的名稱, 預設為format
.defaultContentType(MediaType.APPLICATION_JSON) // 全域性的預設返回型別
.mediaType("xml", MediaType.APPLICATION_XML) // 引數值與對應的型別XML
.mediaType("json", MediaType.APPLICATION_JSON); // 引數值與對應的型別JSON
}
}
再次回憶一下最開始的第一個方法:
@GetMapping(value="/id")
public Student findById(String id) {
return studentService.findById(id);
}
不需要任何的特殊說明,是最優的程式碼。這次不需要任何改造。
訪問一下:http://localhost:8080/student/id?id=12&format=json
得到的結果:
{"id":"12","name":"Jerry","age":25}
訪問一下:http://localhost:8080/student/id?id=12&format=xml
得到的結果:
<Student>
<id>12</id>
<name>Jerry</name>
<age>25</age>
</Student>
我們只在最開始增加了一個統一的配置,之後的方法不用做任何改動就支援了 json 與 xml 兩種格式。還可以通過擴充套件配置支援更多的格式。
訪問一下:http://localhost:8080/student/id?id=12
得到了之前配置的預設格式。
訪問一下:http://localhost:8080/student/id?id=12&format=xxx
如果給一個不支援的格式,直接返回406.
完整程式碼
可以在GitHub找到完整程式碼。
本節程式碼均在該包下:package com.example.demo.lesson20;
下節預覽
關於 JAXB 在 Spring 中的使用就介紹完了,下一節將是整個系類課程的總結。