最近做项目过程中遇到一个需求,需要在java端向外部服务器发送restful请求,并且请求体和返回体都是xml格式数据。经过一番查询,决定使用WebClient和jackson-dataformat-xml解决问题。
一、安装依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
二、使用webClient
项目需要使用https,忽略ssl验证。
package com.sy.ai.vcm.utils;
import com.sy.ai.common.exception.http.ArgumentsException;
import com.sy.ai.common.properties.ProjProperties;
import com.sy.ai.common.constant.ResultCode;
import com.sy.ai.common.utils.XmlUtils;
import com.sy.ai.common.exception.http.HttpException;
import com.sy.ai.vcm.entity.request.VcmBaseRequestEntity;
import com.sy.ai.vcm.entity.response.VcmBaseResponseEntity;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import javax.annotation.PostConstruct;
import java.util.*;
@Component
@Slf4j
public class WebClientUtil {
@Autowired
private ProjProperties projProperties;
private WebClient webClient;
private HashMap<String, String> cookiesMap;
private static final String SESSION_ID = "JSESSIONID";
private static final HttpClient httpClient = HttpClient.create()
.secure(t -> t.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE)));
// .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
// .responseTimeout(Duration.ofMillis(60000));
@PostConstruct
public void setProperties(){
webClient = WebClient.builder()
// 忽略ssl
.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl(projProperties.getVcm().get("base-url"))
.build();
}
public String request(HttpMethod method, MediaType mediaType, String url){
return request(method, mediaType,url,null);
}
public String request(HttpMethod method, MediaType mediaType, String url, Object data){
WebClient.RequestBodySpec spec = webClient.method(method)
.uri(url)
.contentType(mediaType);
WebClient.RequestHeadersSpec headersSpec = spec;
if(!ObjectUtils.isEmpty(cookiesMap)){
headersSpec = spec.cookie(SESSION_ID, cookiesMap.get(SESSION_ID));
}
if(!ObjectUtils.isEmpty(data)){
headersSpec = spec.body(BodyInserters.fromValue(data));
}
Mono<String> mono = headersSpec.exchangeToMono( response -> {
ClientResponse res = (ClientResponse)response;
HttpStatus status = res.statusCode();
List<String> tempCookies = res.headers().header("set-cookie");
if(!ObjectUtils.isEmpty(tempCookies)){
String str = tempCookies.get(0);
String[] arr = str.split(";");
if(!ObjectUtils.isEmpty(arr)){
for (int i = 0; i < arr.length; i++) {
String[] sessionArr = arr[i].split("=");
if(!ObjectUtils.isEmpty(sessionArr) && sessionArr.length > 1){
if(sessionArr[0].equals(SESSION_ID)){
cookiesMap = new HashMap<>();
cookiesMap.put(sessionArr[0], sessionArr[1]);
break;
}
}
}
}
}
if(status.equals(HttpStatus.OK)){
return res.bodyToMono(String.class);
} else {
throw new ArgumentsException(ResultCode.ARGUMENTS_ERROR);
}
});
String responseBody = mono.block();
return responseBody;
}
}
三、jackson-dataformat-xml简单使用
编解码函数
package com.sy.ai.common.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.sy.ai.common.constant.ResultCode;
import com.sy.ai.common.exception.http.HttpException;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Slf4j
public class XmlUtils {
private static XmlMapper xmlMapper = new XmlMapper();
public static <T> T deseralize(String str, Class<T> cls){
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
T response = null;
try {
response = xmlMapper.readValue(inputStream, cls);
} catch (IOException e) {
log.error("xml反编码出错");
e.printStackTrace();
throw new HttpException(ResultCode.INTERVAL_ERROR);
}
return response;
}
public static <T> String serialize(T requestObj) {
xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
String req;
try {
req = xmlMapper.writeValueAsString(requestObj);
} catch (JsonProcessingException e) {
log.error("xml编码出错");
throw new HttpException(ResultCode.INTERVAL_ERROR);
}
return req;
}
}
Entity编写
// 根元素标签名
@JacksonXmlRootElement(localName = "response")
// 定义节点名称
@JacksonXmlProperty(localName = "result")
// 定义包裹列表的节点名称
@JacksonXmlElementWrapper(localName = "plateList")
//使用时,定义了一个plateList标签包裹的plate节点列表
@JacksonXmlProperty(localName = "plate")
@JacksonXmlElementWrapper(localName = "plateList")
private List<Plate> plateList;
// 忽略空字段的编码与反编码
@JsonInclude(JsonInclude.Include.NON_NULL)
这一步也可以在yaml文件中进行全局配置
spring:
jackson:
default-property-inclusion: non_null