相對之前寫的進行簡化和優化 Jaxb工具類
生産版,使用JDK自帶xjc指令從xsd生成的java類,包含了xsd驗證,安全轉換xml.
xjc Xx.xsd -p com.taylor.jaxb
com.taylor.jaxb為生成的java類的包名
或者
java -Dfile.encoding=UTF-8 -cp "C:\Program Files\Java\jdk1.8.0_191\lib\tools.jar" com.sun.tools.internal.xjc.Driver -p com.taylor.jaxb 1-Xx.xsd -npa -no-header -nv
需要增加依賴
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
package com.taylor.xml.customs.datahub;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.validation.SchemaFactory;
import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import org.jaxen.dom4j.Dom4jXPath;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.xml.sax.SAXParseException;
@Slf4j
@SuppressWarnings({"unchecked"})
public class JaxbUtil {
static Map<String, JAXBContext> jaxbContextMap = new HashMap<>();
public static String convert(Object source) {
String convert = null;
ByteArrayOutputStream os;
try {
Class<?> aClass = source.getClass();
JAXBContext jaxbContext = getJaxbContext(aClass);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
@Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
String ds = "http://www.w3.org/2000/09/xmldsig#";
String eCommerce = "http://ebxml.customs.go.th/XMLSchema/EECResponseMsg_2_00";
if (ds.equals(namespaceUri)) {
return "ds";
} else if (eCommerce.equals(namespaceUri)) {
return "eCommerce";
}
return suggestion;
}
});
os = new ByteArrayOutputStream();
marshaller.marshal(source, os);
StringWriter writer = new StringWriter();
marshaller.marshal(source, writer);
convert = writer.toString();
convert = convert.replaceAll(">\\s*<", "><");
} catch (JAXBException e) {
log.error("convert Exception: ",e);
}
return convert;
}
private static JAXBContext getJaxbContext(Class<?> aClass) throws JAXBException {
JAXBContext jaxbContext = jaxbContextMap.get(aClass.getName());
if (jaxbContext == null) {
// 如果每次都調用JAXBContext.newInstance方法,會導緻性能急劇下降
jaxbContext = JAXBContext.newInstance(aClass);
jaxbContextMap.put(aClass.getName(), jaxbContext);
}
return jaxbContext;
}
public static <T> T unconvert(String content, Class<T> clazz) {
try {
Unmarshaller unmarshaller = getUnmarshaller(clazz);
Object obj = unmarshaller.unmarshal(new StringReader(content));
return (T) obj;
} catch (JAXBException e) {
log.error("unconvert JAXBException: ",e);
}
return null;
}
public static Exception handler(String info, Class<?> clazz, Exception e) {
StringBuffer sb = new StringBuffer();
if (StringUtils.isNotBlank(info)) {
if (e.getCause() instanceof SAXParseException ) {
sb.append("封包驗證失敗, ");
}
String type = "解析失敗";
String number = "解析失敗";
try {
Document document = DocumentHelper.parseText(info);
Element element = document.getRootElement();
type = element.getName();
number = getElementVal(document,"//ns:DocumentControl/ns:ReferenceNumber",
clazz.getAnnotation(XmlRootElement.class).namespace());
} catch (DocumentException | JaxenException var8) {
log.error("unconvert Exception: ",e);
}
sb.append("封包[類型:").append(type).append(" - ReferenceNumber:").append(number).append("]無法轉換為:").append(clazz.getSimpleName()).append("執行個體, 原因:\n").append((
e.getCause() instanceof SAXParseException?e.getCause():e).getMessage());
} else {
sb.append(e.toString());
}
return new Exception(sb.toString());
}
/**
* 擷取封包指定節點的值
* 使用xpath進行讀取
* @param xpathString xpath表達式
* @param namespace 預設命名空間
* @return 标簽值 當擷取不到指定的标簽時傳回NULL
*/
@Nullable
public static String getElementVal(Document document, String xpathString, String namespace) throws JaxenException {
/// Node node = document.selectSingleNode(xpath);
XPath xpath = new Dom4jXPath(xpathString);
xpath.addNamespace("ns", namespace);
//對于預設命名空間,我們在解析時要自己添加一個字首
Element ele = (Element)xpath.selectSingleNode(document);
if (Objects.nonNull(ele) && StringUtils.isNotEmpty(ele.getText())) {
return ele.getText();
}
return null;
}
/**
* 将XML轉為指定的XX封包POJO 并做xsd驗證
* @param clazz XX封包類型
* @param xmlStr xml字元串
* @param <T> 需要轉換的POJO類
* @return POJO實體 (實際一定能轉換,是以忽略了轉換警告)
* @throws JAXBException XML格式異常
* @throws IOException IO異常
*/
@NonNull
public static <T> T xmlStrToXx(@NonNull String xmlStr,@NonNull Class<T> clazz) throws Exception {
try {
try (Reader reader = new StringReader(xmlStr)){
Unmarshaller unmarshaller = getUnmarshaller(clazz);
URL schema = clazz.getResource(EECDCLType.getXsdByClass(clazz));
Assert.notNull(schema,"xsd不能為空");
unmarshaller.setSchema(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schema));
return clazz.cast(unmarshaller.unmarshal(reader));
}
} catch (Exception e) {
throw handler(xmlStr,clazz,e);
}
}
private static <T> Unmarshaller getUnmarshaller(@NonNull Class<T> clazz) throws JAXBException {
return getJaxbContext(clazz).createUnmarshaller();
}
public static void main(String[] args) {
try {
EECCancelDeclarationType type = xmlStrToXx("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<EECCancelDeclaration xmlns=\"http://ebxml.customs.go.th/XMLSchema/EECCancelDeclaration_2_00\">\n" +
" <DocumentControl>\n" +
" <ReferenceNumber>AYUG300000006</ReferenceNumber>\n" +
" <DocumentType>I</DocumentType>\n" +
" <DocumentNumber>A015I620700003</DocumentNumber>\n" +
" <CompanyInfo>\n" +
" <TaxNumber>0107557000101</TaxNumber>\n" +
" <Branch>0000</Branch>\n" +
" </CompanyInfo>\n" +
" <CancelReason>For Test Cancel Type I</CancelReason>\n" +
" <RegistrationID>TH0101075570001010000000009T3</RegistrationID>\n" +
" </DocumentControl>\n" +
"</EECCancelDeclaration>", EECCancelDeclarationType.class);
System.out.println(type);
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}