1.需要用的java环境变量的rt.jar 中xml工具;
2.cxf包,主要是针对webservice的发布;
1.首先引入依赖:
java
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.5.6<version>
</dependency>
2.创建cxf的配置类:
java
package com.zfsw.spzx.openapi.service.web.config;
import com.zfsw.spzx.openapi.service.XXXXWebService;
import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.annotation.Resource;
import javax.xml.ws.Endpoint;
@Configuration
@DependsOn("coreApplicationContextAware")
public class CxfConfig {
@Resource
private Bus cxfBus;
@Resource
private XXXXWebService webService;
/**
* 1. 注册CXF Servlet(替代web.xml配置)
*/
@Bean
public ServletRegistrationBean<CXFServlet> cxfServlet() {
ServletRegistrationBean<CXFServlet> servlet = new ServletRegistrationBean<>(new CXFServlet(), "/ws/*");
servlet.setLoadOnStartup(1);
return servlet;
}
/**
* 2. 发布WebService端点
*/
@Bean
public Endpoint userServiceEndpoint() {
EndpointImpl endpoint = new EndpointImpl(cxfBus, webService);
endpoint.publish("/xxxxService");
return endpoint;
}
}
3.入口方法:
java
@RequestMapping(path = "/getKPIValue", method = RequestMethod.POST, consumes = {"application/xml"}, produces = {"application/xml"})
@ApiOperation(value = "XXXX-运行指标统计业务")
@PassToken
@PassResultAdvice
public Object getKPIValue(@RequestBody String param) throws JAXBException, UnsupportedEncodingException {
log.info("XXXX系统请求参数:{}", param);
return xxxxWebService.getKPIValue(param);
}
4.xml解析类工具
java
package com.zfsw.spzx.openapi.service.util;
import com.zfsw.spzx.openapi.service.dto.XXXXInfosReq;
import com.zfsw.spzx.openapi.service.dto.XXXXInfosResp;
import org.springframework.util.StringUtils;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashSet;
import java.util.Set;
public class JaxbXmlParser {
private static final Set<Class<?>> REGISTERED_CLASSES = new HashSet<>();
private static JAXBContext globalContext;
static {
// 预注册常见类
registerClass(XXXXInfosReq.class);
registerClass(XXXXInfosReq.Api.class);
registerClass(XXXXInfosResp.class);
registerClass(XXXXInfosResp.ApiData.class);
registerClass(XXXXInfosResp.Corporation.class);
}
/**
* 注册JAXB类(线程安全)
*/
public static synchronized void registerClass(Class<?> clazz) {
if (REGISTERED_CLASSES.add(clazz)) {
globalContext = null;
}
}
/**
* 批量注册类
*/
public static void registerClasses(Class<?>... classes) {
for (Class<?> clazz : classes) {
registerClass(clazz);
}
}
/**
* 获取或创建JAXBContext(包含所有注册的类)
*/
private static synchronized JAXBContext getGlobalContext() throws JAXBException {
if (globalContext == null) {
globalContext = JAXBContext.newInstance(
REGISTERED_CLASSES.toArray(new Class<?>[0])
);
}
return globalContext;
}
/**
* 获取XML声明中的编码信息
*/
public static String getEncodingFromXmlDeclaration(String xml) {
if (xml == null) {
return null;
}
// 匹配XML声明中的编码属性
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(
"<\\?xml[^>]*encoding=[\"']([^\"']+)[\"']");
java.util.regex.Matcher matcher = pattern.matcher(xml);
if (matcher.find()) {
return matcher.group(1);
}
return null; // 没有找到编码声明
}
/**
* 核心方法:清理 XML 字符串
* @param xmlStr 原始 XML 字符串
* @return 清理后的 XML 字符串(无首尾空白、无注释、标签间空白简化)
*/
public static String cleanXml(String xmlStr) {
if (!StringUtils.hasText(xmlStr)) {
return "";
}
// 步骤1:移除所有 XML 注释(<!-- 任意内容 -->,包括多行注释)
String noCommentXml = removeXmlComments(xmlStr);
// 步骤2:清理首尾所有空白字符(换行、空格、制表符、回车等)
String trimXml = trimAllWhitespace(noCommentXml);
// 步骤3:可选:清理标签之间的多余空白(保留标签内文本/属性的空白)
return simplifyWhitespaceBetweenTags(trimXml);
}
/**
* 移除 XML 注释(<!-- ... -->)
* 正则说明:
* <!-- 匹配注释开始
* [\\s\\S]*? 匹配任意字符(\\s=空白,\\S=非空白,*?=非贪婪匹配,避免匹配到最后一个-->)
* --> 匹配注释结束
*/
private static String removeXmlComments(String xmlStr) {
// 正则匹配所有 XML 注释(包括多行)
String commentRegex = "<!--[\\s\\S]*?-->";
return xmlStr.replaceAll(commentRegex, "");
}
/**
* 清理字符串首尾的所有空白字符(换行、回车、制表符、空格等)
* 比 String.trim() 更彻底(trim() 仅处理半角空格、\n、\r)
*/
private static String trimAllWhitespace(String xmlStr) {
if (xmlStr == null) {
return null;
}
int len = xmlStr.length();
int st = 0;
char[] val = xmlStr.toCharArray();
// 跳过开头的空白字符
while ((st < len) && Character.isWhitespace(val[st])) {
st++;
}
// 跳过结尾的空白字符
while ((st < len) && Character.isWhitespace(val[len - 1])) {
len--;
}
return ((st > 0) || (len < xmlStr.length())) ? xmlStr.substring(st, len) : xmlStr;
}
/**
* 简化标签之间的多余空白(仅保留单个空格,避免换行/多空格干扰)
* 说明:不修改标签内的文本/属性空白(如 <Time> 2025-11-11 11:11:11 </Time> 内的空格保留)
* 正则说明:
* > 匹配标签结束符
* \\s+ 匹配一个或多个空白字符(换行/空格/制表符)
* < 匹配标签开始符
* 替换为 > < 保留标签间单个空格
*/
private static String simplifyWhitespaceBetweenTags(String xmlStr) {
String whitespaceRegex = ">(\\s+)<";
return xmlStr.replaceAll(whitespaceRegex, "><");
}
/**
* 将XML字符串解析为对象
*/
public static <T> T parseXmlToObject(String xmlString, Class<T> clazz) throws JAXBException, UnsupportedEncodingException {
// 注册目标类
registerClass(clazz);
JAXBContext jaxbContext = getGlobalContext();
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
String originalEncoding = getEncodingFromXmlDeclaration(xmlString);
String realXmlString = cleanXml(xmlString);
if (StringUtils.hasText(originalEncoding)) {
// 修改编码声明
realXmlString = new String(realXmlString.getBytes(originalEncoding),originalEncoding);
}
StringReader reader = new StringReader(realXmlString);
return clazz.cast(unmarshaller.unmarshal(reader));
}
/**
* 将对象序列化为XML字符串
*/
public static String objectToXml(Object obj,String encoding) throws JAXBException {
JAXBContext jaxbContext = getGlobalContext();
Marshaller marshaller = jaxbContext.createMarshaller();
if(StringUtils.hasText(encoding)){
// 设置编码
marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
}
// 格式化输出
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter writer = new StringWriter();
marshaller.marshal(obj, writer);
return writer.toString();
}
}
5.入参:
1)标准请求参数(示例)
java
<?xml version="1.0" encoding="gb2312"?>
<info>
<!--传入的地域代码可以为一个或多个-->
<CorporationCode>地域代码1,地域代码2,地域代码3....</CorporationCode>
<!--传入的指标名称可以为一个或多个-->
<Time>时间值</Time>
<api name="指标名称1"></api>
<api name="指标名称2"></api>
<api name="指标名称n"></api>
</info>
2)自定入参数:
java
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement(name = "info")
@XmlAccessorType(XmlAccessType.FIELD)
public class XXXXInfosReq {
@XmlElement(name = "CorporationCode")
private String corporationCodes;
@XmlElement(name = "Time")
private String time;
@XmlElement(name = "api")
private List<Api> apis;
private LocalDateTime dateTime;
@XmlAccessorType(XmlAccessType.FIELD)
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Api {
@XmlAttribute(name = "name")
private String name;
}
}
6.出参
1).标准返回参数(示例)
java
<?xml version="1.0" encoding="gb2312"?>
<return>
<status>success/failure</status>
<message>执行的结果提示</message>
<!-- 若status的值为failure,reason节点才存在 -->
<reason>出错的原因</reason>
<Corporation id="地域代码1">
<api name="指标名称1">
<value>xx</value>
</api>
<api name="指标名称2">
<value>xx</value>
</api>
<api name="指标名称n">
<value>xx</value>
</api>
</Corporation>
<Corporation id="地域代码2">
<api name="指标名称1">
<value>xx</value>
</api>
<api name="指标名称2">
<value>xx</value>
</api>
<api name="指标名称n">
<value>xx</value>
</api>
</Corporation>
...
<Corporation id="地域代码N">
<api name="指标名称1">
<value>xx</value>
</api>
<api name="指标名称2">
<value>xx</value>
</api>
<api name="指标名称n">
<value>xx</value>
</api>
</Corporation>
</return>
2)自定义返参数:
java
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement(name = "return")
@XmlAccessorType(XmlAccessType.FIELD)
public class XXXXInfosResp {
public static final String SUCCESS_STATUS = "success";
public static final String FAIL_STATUS = "fail";
@XmlElement(name = "status")
private String status;
@XmlElement(name = "message")
private String message;
@XmlElement(name = "reason")
private String reason;
@XmlElement(name = "Corporation")
private List<Corporation> corporations;
@XmlAccessorType(XmlAccessType.FIELD)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Corporation {
@XmlAttribute(name = "id")
private String id;
@XmlElement(name = "api")
private List<ApiData> apis;
}
@XmlAccessorType(XmlAccessType.FIELD)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class ApiData {
@XmlAttribute(name = "name")
private String name;
@XmlElement(name = "value")
private String value;
}
}
7.访问ws服务地址:
http://127.0.0.1:62380/ws/xxxxService?wsdl
如下图,表示服务端发布成功:

请求接口地址:
http://127.0.0.1:62380/v1/getKPIValue
返回如下图,则表示成功:
