一、写在所有之前:
因为dom4j和jdom在这个问题上处理的方法是一模一样的,只是一个是SAXBuilder 一个SAXReader,这里以jdom距离,至于dom4j只需要同理替换一下就可以了。
二、问题发生的情况
当你用jdom读取一个有dtd验证的xml文件,同时你的网络是不通的情况下。会出现以下错误:
1,代码如下
package
dom;
import java.io.File;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
public class TestJdom {
publicstaticvoidmain(String[]args){
Filefile=newFile("./src/dom/aiwf_aiService.xml");
if(file.exists()){
SAXBuilderbuilder=newSAXBuilder();
try{
Documentdoc=builder.build(file);
System.out.println(doc);
}catch(Exceptione){
e.printStackTrace();
}
}else{
System.out.println("cannotfindxmlfile:"
+file.getAbsolutePath());
}
}
}
import java.io.File;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
public class TestJdom {
publicstaticvoidmain(String[]args){
Filefile=newFile("./src/dom/aiwf_aiService.xml");
if(file.exists()){
SAXBuilderbuilder=newSAXBuilder();
try{
Documentdoc=builder.build(file);
System.out.println(doc);
}catch(Exceptione){
e.printStackTrace();
}
}else{
System.out.println("cannotfindxmlfile:"
+file.getAbsolutePath());
}
}
}
2,xml文件
<?
xmlversion="1.0"encoding="GBK"
?>
<! DOCTYPEworkflowPUBLIC"-//OpenSymphonyGroup//DTDOSWorkflow2.8//EN""http://www.opensymphony.com/osworkflow/workflow_2_8.dtd" >
< workflow >
...............
</ workflow >
<! DOCTYPEworkflowPUBLIC"-//OpenSymphonyGroup//DTDOSWorkflow2.8//EN""http://www.opensymphony.com/osworkflow/workflow_2_8.dtd" >
< workflow >
...............
</ workflow >
3,错误如下
java.net.SocketException:Permissiondenied:connect
atjava.net.PlainSocketImpl.socketConnect(NativeMethod)
atjava.net.PlainSocketImpl.doConnect(PlainSocketImpl.java: 333 )
atjava.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java: 195 )
atjava.net.PlainSocketImpl.connect(PlainSocketImpl.java: 182 )
atjava.net.Socket.connect(Socket.java: 507 )
atjava.net.Socket.connect(Socket.java: 457 )
atsun.net.NetworkClient.doConnect(NetworkClient.java: 157 )
atsun.net.www.http.HttpClient.openServer(HttpClient.java: 365 )
atsun.net.www.http.HttpClient.openServer(HttpClient.java: 477 )
atsun.net.www.http.HttpClient. < init > (HttpClient.java: 214 )
atsun.net.www.http.HttpClient.New(HttpClient.java: 287 )
atsun.net.www.http.HttpClient.New(HttpClient.java: 299 )
atsun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java: 792 )
atsun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java: 744 )
atsun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java: 669 )
atsun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java: 913 )
atcom.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java: 973 )
atcom.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java: 905 )
atcom.sun.org.apache.xerces.internal.impl.XMLEntityManager.startDTDEntity(XMLEntityManager.java: 872 )
atcom.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.setInputSource(XMLDTDScannerImpl.java: 282 )
atcom.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDispatcher.dispatch(XMLDocumentScannerImpl.java: 1021 )
atcom.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java: 368 )
atcom.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java: 834 )
atcom.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java: 764 )
atcom.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java: 148 )
atcom.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java: 1242 )
atorg.jdom.input.SAXBuilder.build(SAXBuilder.java: 453 )
atorg.jdom.input.SAXBuilder.build(SAXBuilder.java: 810 )
atorg.jdom.input.SAXBuilder.build(SAXBuilder.java: 789 )
atdom.TestJdom.main(TestJdom.java: 26 )
atjava.net.PlainSocketImpl.socketConnect(NativeMethod)
atjava.net.PlainSocketImpl.doConnect(PlainSocketImpl.java: 333 )
atjava.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java: 195 )
atjava.net.PlainSocketImpl.connect(PlainSocketImpl.java: 182 )
atjava.net.Socket.connect(Socket.java: 507 )
atjava.net.Socket.connect(Socket.java: 457 )
atsun.net.NetworkClient.doConnect(NetworkClient.java: 157 )
atsun.net.www.http.HttpClient.openServer(HttpClient.java: 365 )
atsun.net.www.http.HttpClient.openServer(HttpClient.java: 477 )
atsun.net.www.http.HttpClient. < init > (HttpClient.java: 214 )
atsun.net.www.http.HttpClient.New(HttpClient.java: 287 )
atsun.net.www.http.HttpClient.New(HttpClient.java: 299 )
atsun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java: 792 )
atsun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java: 744 )
atsun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java: 669 )
atsun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java: 913 )
atcom.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java: 973 )
atcom.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java: 905 )
atcom.sun.org.apache.xerces.internal.impl.XMLEntityManager.startDTDEntity(XMLEntityManager.java: 872 )
atcom.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.setInputSource(XMLDTDScannerImpl.java: 282 )
atcom.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDispatcher.dispatch(XMLDocumentScannerImpl.java: 1021 )
atcom.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java: 368 )
atcom.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java: 834 )
atcom.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java: 764 )
atcom.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java: 148 )
atcom.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java: 1242 )
atorg.jdom.input.SAXBuilder.build(SAXBuilder.java: 453 )
atorg.jdom.input.SAXBuilder.build(SAXBuilder.java: 810 )
atorg.jdom.input.SAXBuilder.build(SAXBuilder.java: 789 )
atdom.TestJdom.main(TestJdom.java: 26 )
三、分析原因
当执行build的时候jdom分析到
DOCTYPEworkflowPUBLIC"-/OpenSymphonyGroup//DTDOSWorkflow2.8//EN""http://www.opensymphony.com/osworkflow/workflow_2_8.dtd
就会去读取http://www.opensymphony.com/osworkflow/workflow_2_8.dtd这里的dtd文件来验证,但是因为网络是不通的所以就会报socket错误。
四、解决办法
1,最开始查看jdom api发现了这样一个方法
builder.setValidation(false);
这样可以让jdom不做验证,但是结果依然出问题,查了一下原因,说虽然不验证但是还是会下载
2,参照jdom网站的FAQ http://www.jdom.org/docs/faq.html#a0100
这是原文内容
HowdoIkeeptheDTDfromloading?EvenwhenIturnoffvalidationtheparsertriestoloadtheDTDfile.
Evenwhenvalidationisturnedoff,anXMLparserwillbydefaultloadtheexternalDTDfileinordertoparsetheDTDforexternalentitydeclarations.Xerceshasafeaturetoturnoffthisbehaviornamed"http://apache.org/xml/features/nonvalidating/load-external-dtd"andifyouknowyou'reusingXercesyoucansetthisfeatureonthebuilder.
builder.setFeature(
"http://apache.org/xml/features/nonvalidating/load-external-dtd",false);
Ifyou'reusinganotherparserlikeCrimson,yourbestbetistosetupanEntityResolverthatresolvestheDTDwithoutactuallyreadingtheseparatefile.
importorg.xml.sax.*;
importjava.io.*;
publicclassNoOpEntityResolverimplementsEntityResolver{
publicInputSourceresolveEntity(StringpublicId,StringsystemId){
returnnewInputSource(newStringBufferInputStream(""));
}
}
Theninthebuilder
builder.setEntityResolver(newNoOpEntityResolver());
Thereisadownsidetothisapproach.Anyentitiesinthedocumentwillberesolvedtotheemptystring,andwilleffectivelydisappear.Ifyourdocumenthasentities,youneedtosetExpandEntities(false)codeandensuretheEntityResolveronlysuppressestheDocType.
里边教我们定义个类
Evenwhenvalidationisturnedoff,anXMLparserwillbydefaultloadtheexternalDTDfileinordertoparsetheDTDforexternalentitydeclarations.Xerceshasafeaturetoturnoffthisbehaviornamed"http://apache.org/xml/features/nonvalidating/load-external-dtd"andifyouknowyou'reusingXercesyoucansetthisfeatureonthebuilder.
builder.setFeature(
"http://apache.org/xml/features/nonvalidating/load-external-dtd",false);
Ifyou'reusinganotherparserlikeCrimson,yourbestbetistosetupanEntityResolverthatresolvestheDTDwithoutactuallyreadingtheseparatefile.
importorg.xml.sax.*;
importjava.io.*;
publicclassNoOpEntityResolverimplementsEntityResolver{
publicInputSourceresolveEntity(StringpublicId,StringsystemId){
returnnewInputSource(newStringBufferInputStream(""));
}
}
Theninthebuilder
builder.setEntityResolver(newNoOpEntityResolver());
Thereisadownsidetothisapproach.Anyentitiesinthedocumentwillberesolvedtotheemptystring,andwilleffectivelydisappear.Ifyourdocumenthasentities,youneedtosetExpandEntities(false)codeandensuretheEntityResolveronlysuppressestheDocType.
public
class
NoOpEntityResolver
implements
EntityResolver
{
publicInputSourceresolveEntity(StringpublicId,StringsystemId){
returnnewInputSource(newStringBufferInputStream(""));
}
}
publicInputSourceresolveEntity(StringpublicId,StringsystemId){
returnnewInputSource(newStringBufferInputStream(""));
}
}
通过builder.setEntityResolver(newNoOpEntityResolver())方法来隐蔽起dtd验证器。这样就不会出错了。试了一下确实没问题了。但要知道xml没有dtd验证是不好的,我们是否能让它使用本地dtd验证呢。例如本文的oswork
我把验证文件workflow_2_8.dtd拷贝到本地,能否验证的时候用本地的呢?
3,用本地dtd验证
方法有两种
方法一、更改xml中的doctype声明,但是一般情况下更改这个是不好的。更改后就不是标准的了。
方法二、验证期替换
看到上边FAQ讲的方法你是否有什么灵感呢?
看看下边这段代码
package
dom;
import java.io.File;
import java.io.IOException;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class TestJdom {
publicstaticvoidmain(String[]args){
Filefile=newFile("./src/dom/aiwf_aiService.xml");
if(file.exists()){
SAXBuilderbuilder=newSAXBuilder();
builder.setValidation(false);
builder.setEntityResolver(newEntityResolver(){
publicInputSourceresolveEntity(StringpublicId,
StringsystemId)throwsSAXException,IOException{
returnnewInputSource("./workflow_2_8.dtd");
}
});
try{
Documentdoc=builder.build(file);
System.out.println(doc);
}catch(Exceptione){
e.printStackTrace();
}
}else{
System.out.println("cannotfindxmlfile:"
+file.getAbsolutePath());
}
}
}
import java.io.File;
import java.io.IOException;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class TestJdom {
publicstaticvoidmain(String[]args){
Filefile=newFile("./src/dom/aiwf_aiService.xml");
if(file.exists()){
SAXBuilderbuilder=newSAXBuilder();
builder.setValidation(false);
builder.setEntityResolver(newEntityResolver(){
publicInputSourceresolveEntity(StringpublicId,
StringsystemId)throwsSAXException,IOException{
returnnewInputSource("./workflow_2_8.dtd");
}
});
try{
Documentdoc=builder.build(file);
System.out.println(doc);
}catch(Exceptione){
e.printStackTrace();
}
}else{
System.out.println("cannotfindxmlfile:"
+file.getAbsolutePath());
}
}
}
对了,同样是自己实现一个EntityResolver(这里用了匿名类),不同的是在里边使用本地的dtd验证
另外,匿名类内部,似乎这样写起来更顺眼些
InputStreamstream
=
new
FileInputStream(
"
yourdtdfilepath
"
);
InputSourceis = new InputSource(stream);
is.setPublicId(publicId);
is.setSystemId(systemId);
return is;
InputSourceis = new InputSource(stream);
is.setPublicId(publicId);
is.setSystemId(systemId);
return is;