上一篇文章:JavaBean 和 XML 互相轉換(三)
當我們 JavaBean 屬性值中存在特殊字元時,添加到 XML 中就會出現問題,有兩種方式可以處理:
- 一種方式是将特殊字元轉義
- 另外一種是使用 <![CDATA[]]> 包裹
對于 Hutool 提供的 JAXBUtil 預設就是使用第一種方式,今天我們就介紹第二種方式的實作,需要如下步驟:
- 需要添加如下依賴:
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.moxy</artifactId>
<version>2.5.2-M1</version>
</dependency>
- 在需要使用 <![CDATA[]]> 的 JavaBean 屬性上添加 @XmlCDATA 注解
- 需要在 resource 中建立和需要轉義的 JavaBean 同包的目錄,并添加 jaxb.properties 檔案,内容如下:
# 用于配置建立 JAXBContext 的工廠類,該工廠類支援 BeanToXml 添加 CDATA,必須與待轉換的 Bean 同包
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
jaxb.properties 所在的目錄需要 JAXBUtil.beanToXml() 工具入參的類所在的包一緻,為什麼這樣,我們文章最後通過源碼解釋。
測試需要使用的類:
- 學生類:
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
public class Student {
/**
* 學号
*/
@XmlCDATA
private String no;
/**
* 學生姓名
*/
@XmlCDATA
private String name;
/**
* 年齡
*/
private Integer age;
/**
* 家庭位址
*/
private String address;
}
- 測試類:
public class XmlBeanMapperTest {
@Test
public void testCdata() {
Student student = new Student("1001", "<李梅>", 8, "<北京市昌平區>");
Console.log(JAXBUtil.beanToXml(student));
}
}
運作結果如下:
<?xml version="1.0" encoding="UTF-8"?>
<student>
<no><![CDATA[1001]]></no>
<name><![CDATA[<李梅>]]></name>
<age>8</age>
<address><北京市昌平區></address>
</student>
通過上面的示例可以看到,如果标注 @XmlCDATA 注解的屬性會使用 <![CDATA[]]> 包裹,特殊字元也不會被轉義,沒有标注的如果有特殊字元就會被轉義。
下面解釋為什麼 jaxb.properties 所在的目錄需要 JAXBUtil.beanToXml() 工具入參的類所在的包一緻?
我們想看 JAXBUtil.beanToXml() 的源碼,如下:
public static String beanToXml(Object bean) {
return beanToXml(bean, CharsetUtil.CHARSET_UTF_8, true);
}
該方法調用了其另外一個重載方法,如下:
public static String beanToXml(Object bean, Charset charset, boolean format) {
StringWriter writer;
try {
JAXBContext context = JAXBContext.newInstance(bean.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, format);
marshaller.setProperty(Marshaller.JAXB_ENCODING, charset.name());
writer = new StringWriter();
marshaller.marshal(bean, writer);
} catch (Exception e) {
throw new UtilException("convertToXml 錯誤:" + e.getMessage(), e);
}
return writer.toString();
}
該方法中通過 JAXBContext.newInstance() 建構了一個 JAXBContext 執行個體,我們點進 newInstance() 方法看一下:
public static JAXBContext newInstance( Class... classesToBeBound )
throws JAXBException {
return newInstance(classesToBeBound,Collections.<String,Object>emptyMap());
}
該方法調用了另一個重載方法,如下:
public static JAXBContext newInstance( Class[] classesToBeBound, Map<String,?> properties )
throws JAXBException {
// 省略
return ContextFinder.find(classesToBeBound,properties);
}
該方法通過 ContextFinder.find(classesToBeBound,properties) 建立 JAXBContext,點進 find 方法,如下:
static JAXBContext find( Class[] classes, Map properties ) throws JAXBException {
// 省略其他代碼
for (final Class c : classes) {
ClassLoader classLoader = getClassClassLoader(c);
// c 是需要轉換為 XML 的 JavaBean 類
Package pkg = c.getPackage(); // 擷取該類的包資訊
if(pkg==null)
continue;
String packageName = pkg.getName().replace('.', '/'); // 将包中的 . 替換為 /
// 将替換為 / 的包名拼接 jaxb.properties,組成的相對路徑,後續通過該路徑查找檔案,
// 是以需要在 resource 下建立 JavaBean 包相同的目錄儲存 jaxb.properties
String resourceName = packageName+"/jaxb.properties";
// 省略其他代碼
return newInstance(classes, properties, PLATFORM_DEFAULT_FACTORY_CLASS);
}
如果文章對大家有所幫助,歡迎點贊、關注、評論。