背景
在使用阿里云的開放api的SDK的時(shí)候抽减,導(dǎo)入阿里云相關(guān)依賴后核蘸,啟動(dòng)項(xiàng)目時(shí)報(bào)錯(cuò) Caused by: org.xml.sax.SAXNotRecognizedException: unrecognized feature http://xml.org/sax/features/external-general-entities
環(huán)境
JDK 11
Spring boot 2.6.3
Gradle 7.3
原因
經(jīng)過一頓 debug 排查后發(fā)現(xiàn)门躯,是阿里云的包里面有 SAXParserFactory 的實(shí)現(xiàn)類涩金,與默認(rèn)實(shí)現(xiàn)不兼容控硼,導(dǎo)致 logback 初始化報(bào)錯(cuò)枷颊,報(bào)錯(cuò)的關(guān)鍵位置在 ch.qos.logback.core.joran.event.SaxEventRecorder#buildSaxParser戳杀,
private SAXParser buildSaxParser() throws JoranException {
try {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setValidating(false);
//spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
spf.setNamespaceAware(true);
return spf.newSAXParser();
} catch (Exception pce) {
String errMsg = "Parser configuration error occurred";
addError(errMsg, pce);
throw new JoranException(errMsg, pce);
}
}
SAXParserFactory spf = SAXParserFactory.newInstance();
這一句代碼獲取到的 SAXParserFactory 實(shí)例并不是默認(rèn)實(shí)例,而是阿里云的包里面的實(shí)現(xiàn)夭苗,導(dǎo)致下面的spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
報(bào)錯(cuò)信卡。
解決
新的解決方案:
使用之前的解決方案時(shí),發(fā)現(xiàn)根本無法進(jìn)行單元測(cè)試题造,所以需要尋找新的解決方案傍菇。主要思路是找到有問題的 SAXParserFactory 實(shí)現(xiàn),并從依賴中排除界赔。
通過 debug 排查后丢习,找到有問題的實(shí)現(xiàn)類為 org.gjt.xpp.jaxp11.SAXParserFactoryImpl,所在的依賴包為 pull-parser:pull-parser:2淮悼。這個(gè) pull-parser 是什么咐低,可以從依賴中排除掉嗎?
借助 IDEA 的 gradle 依賴分析工具袜腥,很快找到了依賴 pull-parser:pull-parser 的依賴鏈為 com.aliyun:tea-util => com.aliyun:tea-rpc-util => org.dom4j:dom4j => pull-parser:pull-parser见擦,在搜索關(guān)于 pull-parser 信息的時(shí)候,發(fā)現(xiàn)了 dom4j 庫(kù)的這條 issue羹令,可以看到其實(shí) pull-parser 僅僅是可選依賴鲤屡,而且已經(jīng)有和 SAXParser 相關(guān)的沖突被提出了「3蓿看到這里我已經(jīng)不需要知道 pull-parser 是什么了酒来,我只知道可以從依賴中排除出去。
接下來只需要從依賴中排除 pull-parser:pull-parser 就可以了肪凛,在有依賴 com.aliyun:tea-util 的地方排除 pull-parser:pull-parser堰汉,類似下面的代碼,
implementation('com.aliyun:bssopenapi20171214:1.7.8'){
exclude group: "pull-parser", module: "pull-parser"
}
implementation ('com.aliyun:rds20140815:1.0.4'){
exclude group: "pull-parser", module: "pull-parser"
}
問題解決显拜。
之前的解決方案:
SAXParserFactory 是一個(gè) XML 文檔解析器的工廠接口衡奥,查看了源碼后可找到初始化該類實(shí)例的 關(guān)鍵位置為 javax.xml.parsers.FactoryFinder#find,
static <T> T find(Class<T> type, String fallbackClassName)
throws FactoryConfigurationError
{
final String factoryId = type.getName();
dPrint(()->"find factoryId =" + factoryId);
// Use the system property first
try {
String systemProp = SecuritySupport.getSystemProperty(factoryId);
if (systemProp != null) {
dPrint(()->"found system property, value=" + systemProp);
return newInstance(type, systemProp, null, true);
}
}
catch (SecurityException se) {
if (debug) se.printStackTrace();
}
// try to read from $java.home/conf/jaxp.properties
try {
if (firstTime) {
synchronized (cacheProps) {
if (firstTime) {
String configFile = SecuritySupport.getSystemProperty("java.home") + File.separator +
"conf" + File.separator + "jaxp.properties";
File f = new File(configFile);
firstTime = false;
if (SecuritySupport.doesFileExist(f)) {
dPrint(()->"Read properties file "+f);
cacheProps.load(SecuritySupport.getFileInputStream(f));
}
}
}
}
final String factoryClassName = cacheProps.getProperty(factoryId);
if (factoryClassName != null) {
dPrint(()->"found in ${java.home}/conf/jaxp.properties, value=" + factoryClassName);
return newInstance(type, factoryClassName, null, true);
}
}
catch (Exception ex) {
if (debug) ex.printStackTrace();
}
// Try Jar Service Provider Mechanism
T provider = findServiceProvider(type);
if (provider != null) {
return provider;
}
if (fallbackClassName == null) {
throw new FactoryConfigurationError(
"Provider for " + factoryId + " cannot be found");
}
dPrint(()->"loaded from fallback value: " + fallbackClassName);
return newInstance(type, fallbackClassName, null, true);
}
該方法以三種方式按順序?qū)ふ?SAXParserFactory 的實(shí)現(xiàn)類:
- 查找系統(tǒng)屬性 "javax.xml.parsers.SAXParserFactory"。
- 查找 ${java.home}/conf/jaxp.properties 文件中定義的配置 "javax.xml.parsers.SAXParserFactory"远荠。
- 使用 java.util.ServiceLoader 查找實(shí)現(xiàn)了 SAXParserFactory 的類矮固,這個(gè)我沒有細(xì)看,猜測(cè)是可以查找運(yùn)行環(huán)境中所有實(shí)現(xiàn)了這個(gè)接口的類,然后取第一個(gè)档址。
三種方式都找不到 SAXParserFactory 的實(shí)現(xiàn)類的時(shí)候盹兢,就會(huì)取默認(rèn)實(shí)現(xiàn)com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
。
看到這里已經(jīng)能夠找到解決辦法了守伸,只需要添加系統(tǒng)屬性 "javax.xml.parsers.SAXParserFactory" ,設(shè)置為默認(rèn)實(shí)現(xiàn)com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
就可以了绎秒,即
System.setProperty("javax.xml.parsers.SAXParserFactory", "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
這句代碼放在 main 方法第一行即可。