JAXB 深入显出 - JAXB 教程 Spring Boot返回 XML

x33g5p2x  于2021-12-28 转载在 其他  
字(6.2k)|赞(0)|评价(0)|浏览(382)

摘要: 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 提供了6个实现类,如下:

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

而对于消息转换器的调用,都是在RequestResponseBodyMethodProcessor类中完成的。该类会根据注解中指定返回类型去找对应的转换器,如果找不到,就抛出HttpMediaTypeNotAcceptableException异常,浏览器会收到一个406 状态码。

原来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> 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 中的使用就介绍完了,下一节将是整个系类课程的总结。

相关文章