Java 系列 - SpringBoot-1 - 入参及出参扩展 XML 支持

Java 系列 - SpringBoot-1 - 入参及出参扩展 XML 支持

前言

现在 Java 后端项目基本都使用 json 格式请求和响应数据,因此一般的配置返回 json 格式数据便能够满足需求。但仍有一些场景需要支持 xml 格式的请求和响应数据,针对此类需求,
本文在 SpringBoot 框架上进行配置说明。

注:原 spring mvc 使用 xml 配置也支持此配置。

环境

  • SpringBoot 版本:2.2.8.RELEASE

相关知识点

Http 请求 Header 中 Accept 和 Content-Type 的区别

Http 报头分为通用报头,请求报头,响应报头和实体报头。

请求方的 http 报头结构:通用报头 | 请求报头 | 实体报头

响应方的 http 报头结构:通用报头 | 响应报头 | 实体报头

  • Accept:属于请求头,代表发送端(客户端)希望接受的数据类型。
    • application/xml
    • application/json;java 枚举:MediaType
  • Content-Type:代表发送端(客户端 | 服务器)发送的实体数据的数据类型
    • application/xml
    • application/json;java 枚举:MediaType

常见的媒体格式类型如下:

  • text/html : HTML 格式
  • text/plain :纯文本格式
  • text/xml : XML 格式
  • image/gif :gif 图片格式
  • image/jpeg :jpg 图片格式
  • image/png:png 图片格式
  • multipart/form-data : 表单中进行文件上传时使用该格式

以 application 开头的媒体格式类型:

  • application/xhtml+xml :XHTML 格式
  • application/xml : XML 数据格式
  • application/atom+xml :Atom XML 聚合格式
  • application/json : JSON 数据格式
  • application/pdf :pdf 格式
  • application/msword : Word 文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded :
    中默认的 encType,form 表单数据被编码为 key/value 格式发送到服务器(表单默认的提交数据的格式)

Spring 中 consumes 和 produces 的区别

SpringMvc 中如 @RequestMapping 之类的注解(@PostMapping 等)有 consumesproduces 这两个属性;

  • consumes: 指定处理请求的提交内容类型(Content-Type),例如 application/json, text/html;
  • produces: 指定返回的内容类型,仅当 request 请求头中的 (Accept) 类型中包含该指定类型才返回;
  • params: 指定 request 中必须包含某些参数值是,才让该方法处理
  • headers: 指定 request 中必须包含某些指定的 header 值,才能让该方法处理请求

使用示例:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/api/v1")
public class ApiTestController {
@PostMapping(value = "/pay",
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ApiResponse<TestResponse> testPay(@Validated @RequestBody TestRequest request) {
log.info("测试post方法:【{}】", JSON.toJSONString(request));
TestResponse response = TestResponse.builder()
.field1("field1")
.field2("field2")
.field3("field3")
.build();
return ApiResponse.ok("请求成功", "1234", "业务成功", response);
}
}

SpringMVC 输出格式

在 SpringMVC 使用 ContentNegotiation 来判断用户请求希望得到什么格式的数据。

ContentNegotiation 设置了三种方式来设置相应数据的格式,优先级由上至下:

  • 通过请求 URL 后缀:/list.html 返回 html 格式;
  • 通过请求的参数:/list?format=html 该设置默认不开启,默认 key 是 format。
  • 通过 HTTP Header 的 Accept:Accept:application/xml

本文在 springboot 中配置扩展 xml 格式:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SpringMvc中配置(如需修改默认配置,在SpringBoot中也可这样配置)
// ApiMessageConverterConfig extends WebMvcConfigurerAdapter
@Configuration
public class ApiMessageConverterConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
// 是否通过请求Url的扩展名来决定media type,默认为false(不支持),扩展名指的xxx.json、xxx.xml等形式
.favorPathExtension(true)
// favorParameter:这里是是否启用参数支持,默认是false(不支持);例如 /user/{userid}?format=json
.favorParameter(true)
.parameterName("mediaType")
// 不检查Accept请求头 ,默认是false(不忽略),即请求时指定的contentType:application/json等
.ignoreAcceptHeader(false)
/* 设置默认的media type */
.defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
// mediaTypes:配置扩展名到mimeType的映射,json和xml的映射;
.mediaType("xml", MediaType.APPLICATION_XML)
.mediaType("html", MediaType.TEXT_HTML)
.mediaType("json", MediaType.APPLICATION_JSON);
}
}

pom.xml 中引入依赖:

xml
1
2
3
4
5
<!-- 加入jackson-dataformat-xml依赖,Spring Boot就会自动引入MappingJackson2XmlHttpMessageConverter的实现:-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

请求实体类配置,需要使用上述依赖中 @JacksonXmlRootElement@JacksonXmlProperty 注解:
注:xml 格式的必须指定根节点;

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JacksonXmlRootElement(localName = "request")
public class TestRequest extends ApiPayV1Request<TestResponse> implements Serializable {
private static final long serialVersionUID = -3135169148798610260L;

@JacksonXmlProperty(localName = "field1")
private String field1;
private String field2;
private String field3;

@Override
public Class<TestResponse> getResponseClass() {
return TestResponse.class;
}
}

响应实体类配置,同上

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@JacksonXmlRootElement(localName = "root")
public class ApiResponse<T> implements Serializable {
private static final long serialVersionUID = -3798634638472229369L;

/**
* 是否成功
*/
@JacksonXmlProperty(localName = "success")
private boolean success;
/**
* 返回状态编码
*/
@JacksonXmlProperty(localName = "code")
private String code;
/**
* 返回状态描述
*/
@JacksonXmlProperty(localName = "message")
private String message;
/**
* 返回业务执行状态码
* 需要特殊处理的
*/
@JacksonXmlProperty(localName = "sub_code")
private String sub_code;
/**
* 返回业务执行状态描述
* 需要特殊处理的
*/
@JacksonXmlProperty(localName = "sub_msg")
private String sub_msg;
/**
* 接口调用时间
*/
@JacksonXmlProperty(localName = "time")
@JsonFormat(pattern = "yyyyMMddHHmmssSSS", timezone = "GMT+8")
private Timestamp time;
/**
* 接口返回数据
*/
@JacksonXmlProperty(localName = "data")
private T data;

/**
* 接口签名
*/
@JacksonXmlProperty(localName = "sign")
private String sign;

}

参考资料