1. 程式人生 > >JAXB 深入顯出 - JAXB 教程 Spring Boot返回 XML

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
結果並沒有得到想要的資料,而是返回了錯誤頁面。

406

檢視Eclipse控制檯,並沒有任何錯誤輸出,說明程式碼層面沒有出問題。
檢視頁面控制檯,發現錯誤碼是 406,並且 Accept 已經是 application/xml

深入 406 錯誤

Spring 使用HttpMessageConverter<T> 將請求資訊轉換為一個物件(型別為 T),將物件(型別為 T)輸出為響應資訊。
並且HttpMessageConveter

提供了6個實現類,如下:

  • ByteArrayHttpMessageConverter
  • String HttpMessageConverter
  • ResourceHttpMessageConverter
  • SourceHttpMessageConverter
  • AllEncompassingFormHttpMessageConverter
  • Jaxb2RootElementHttpMessageConverter

而對於訊息轉換器的呼叫,都是在RequestResponseBodyMethodProcessor類中完成的。該類會根據註解中指定返回型別去找對應的轉換器,如果找不到,就丟擲HttpMediaTypeNotAcceptableException

異常,瀏覽器會收到一個406 狀態碼。

Spring

原來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

list

又給了一個 406。明明已經加了註解,怎麼還是不行呢?再次回想之前的章節,原來 JAXB 不支援 List 資料型別,看來需要對現有程式碼改造了。

小小的改造

首先,定義一個 Students類,其中只有一個屬性,就是List<Student> studentList中存放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 中的使用就介紹完了,下一節將是整個系類課程的總結。