我正在尝试运行一个小示例,旨在将目录文件与模式文件一起存储在 Java 包中。目的是获得一个不依赖任何外部文件的自包含包,最终将其打包在 JAR 中,但目前它是文件系统中的普通文件。
但是我在 JAXP 类的深处遇到了 NullPointerException,并且到目前为止无法理解是什么触发了这个异常,以及我应该做什么来摆脱它。
$ java -version
openjdk version "15" 2020-09-15
OpenJDK Runtime Environment (build 15+36-1562)
OpenJDK 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)
$ java -classpath . foo.Foo
java.lang.NullPointerException: JAXP09020006: The argument 'systemId' can not be null.
at java.xml/javax.xml.catalog.CatalogMessages.reportNPEOnNull(CatalogMessages.java:129)
at java.xml/javax.xml.catalog.CatalogResolverImpl.resolveEntity(CatalogResolverImpl.java:70)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.resolveEntity(XMLEntityManager.java:1154)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.resolveDocument(XMLSchemaLoader.java:662)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.findSchemaGrammar(XMLSchemaValidator.java:2694)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:2069)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:829)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:374)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl$NSContentDriver.scanRootElementHook(XMLNSDocumentScannerImpl.java:613)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3078)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:836)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:541)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:888)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:824)
at java.xml/com.sun.org.apache.xerces.internal.jaxp.validation.StreamValidatorHelper.validate(StreamValidatorHelper.java:176)
at java.xml/com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.validate(ValidatorImpl.java:115)
at foo.Foo.main(Foo.java:45)
Java源代码
(./foo/Foo.java
的内容)
package foo;
import java.io.File;
import java.io.StringWriter;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import javax.xml.XMLConstants;
import javax.xml.catalog.CatalogFeatures;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;
public class Foo
{
public static void main(String[] args)
{
final String catalogFile = CatalogFeatures.Feature.FILES.getPropertyName();
final String catalogPath = "foo/catalog.xml";
final ClassLoader classLoader = Foo.class.getClassLoader();
try
{
final URL catalogUrl = classLoader.getResource(catalogPath);
final URI catalog = catalogUrl.toURI();
if (catalog != null)
{
SchemaFactory schemaFactory =
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema();
StreamSource source = new StreamSource(new File("xyzzy.xml"));
Validator validator = schema.newValidator();
validator.setProperty(catalogFile, catalog.toString());
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
validator.validate(source, result); // Triggers NullPointerException
System.out.println(writer);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
目录文件
(./foo/catalog.xml
的内容)
<?xml version="1.0"?>
<!DOCTYPE catalog
PUBLIC "-//OASIS/DTD Entity Resolution XML Catalog V1.0//EN"
"http://www.oasis-open.org/comittees/entity/release/1.0/catalog.dtd">
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<uri name="urn:foo:bar:xyzzy.xsd:0.1"
uri="schemas/xyzzy.xsd"/>
</catalog>
XSD 架构文件
(./foo/schemas/xyzzy.xsd
的内容)
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:foo:bar"
xmlns:gazonk="urn:foo:bar"
elementFormDefault="qualified">
<xs:element name="xyzzy">
<xs:complexType/>
</xs:element>
</xs:schema>
XML 文件 xyzzy.xml
(./xyzzy.xml
的内容)
<?xml version="1.0"?>
<gazonk:xyzzy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gazonk="urn:foo:bar"
xsi:schemaLocation="urn:foo:bar:xyzzy.xsd:0.1">
</gazonk:xyzzy>
我应该怎样做才能消除这个异常?
更新
我想我已经能够弄清楚这里发生了什么,我的直觉告诉我,我成功地触发了 java.xml/javax.xml.catalog.CatalogResolverImpl.resolveEntity
只考虑 systemId 为 null
的情况,无论 publicId 是否为 null
。如果 publicId 不 null
,则 systemId 为 null
的情况是完全可以的。
为了解决这个问题,我所做的就是创建一个包装类,该类实现 CatalogResolver
接口(interface),并在只有 systemId 为 null
的情况下拦截传递调用>(只需将 null
替换为 ""
),以及当 systemId 和 publicId 均为 null
时(抛出一个异常,提供更明智的选择)原因及解释)。您可以在下面找到我修改后的代码。
XML 示例中有一个小错误(它与架构不匹配),匹配的 XML 文件是
<?xml version="1.0"?>
<gazonk:xyzzy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gazonk="urn:foo:bar"
xsi:schemaLocation="urn:foo:bar:xyzzy.xsd:0.1"/>
Java 文件 Resolver.java
(./foo/Resolver.java
的内容)
package foo;
import java.io.InputStream;
import javax.xml.catalog.CatalogResolver;
import javax.xml.transform.Source;
import org.w3c.dom.ls.LSInput;
import org.xml.sax.InputSource;
public class Resolver implements CatalogResolver
{
private final CatalogResolver m_resolver;
public Resolver(CatalogResolver resolver)
{
if (resolver != null)
{
m_resolver = resolver;
}
else
{
String message = "Wrapped resolver must not be null.";
throw new IllegalArgumentException(message);
}
}
public Source resolve(String href, String base)
{
return m_resolver.resolve(href, base);
}
public InputSource resolveEntity(String publicId, String systemId)
{
// Ensure systemId is not null.
return m_resolver.resolveEntity(publicId,
(systemId == null)? "" : systemId);
}
public InputStream resolveEntity(String publicId,
String systemId,
String baseUri,
String namespace)
{
// Ensure systemId is not null.
return m_resolver.resolveEntity(publicId,
(systemId == null)? "" : systemId,
baseUri,
namespace);
}
public LSInput resolveResource(String type,
String namespaceUri,
String publicId,
String systemId,
String baseUri)
{
// Ensure both publicId and systemId are not null at the same time
// before passing it on to the real resolver.
if ((publicId == null) && (systemId == null))
{
String message = ("Missing namespace and schema location pair, " +
"only have namespace URI '" + namespaceUri +
"' which is not enough to go on when trying to " +
"locate the schema file...");
throw new NullPointerException(message);
}
// Ensure systemId is not null.
return m_resolver.resolveResource(type,
namespaceUri,
publicId,
(systemId == null)? "" : systemId,
baseUri);
}
}
Java 文件 Foo.java 的修改部分 (在 if 子句中添加了几行代码以及一些上下文)
...
if (catalog != null)
{
CatalogFeatures features = CatalogFeatures.builder()
.with(CatalogFeatures.Feature.PREFER, "public")
.with(CatalogFeatures.Feature.DEFER, "true")
.with(CatalogFeatures.Feature.RESOLVE, "strict")
.build();
CatalogResolver resolver = CatalogManager.catalogResolver(features,
catalog);
Resolver wrapper = new Resolver(resolver);
SchemaFactory schemaFactory =
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
...
validator.setProperty(catalogFile, catalog.toString());
validator.setResourceResolver(wrapper);
StringWriter writer = new StringWriter();
...
工作结果
$ java -version
openjdk version "15" 2020-09-15
OpenJDK Runtime Environment (build 15+36-1562)
OpenJDK 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)
$ java -classpath . foo.Foo
<?xml version="1.0" encoding="UTF-8"?><gazonk:xyzzy xmlns:gazonk="urn:foo:bar">
</gazonk:xyzzy>
最佳答案
使用jaxb2-maven-plugin时也会出现同样的问题,因为没有编程选项,所以无法轻松解决该问题。为了弥补这个问题,我们可以创建一个类:
package com.sun.tools.xjc;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import javax.xml.catalog.CatalogFeatures;
import javax.xml.catalog.CatalogManager;
import javax.xml.catalog.CatalogResolver;
import org.xml.sax.EntityResolver;
public class CatalogUtil {
static EntityResolver getCatalog(EntityResolver entityResolver, File catalogFile, ArrayList<URI> catalogUrls) throws IOException {
if (entityResolver != null) {
return entityResolver;
}
CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.builder().build(), catalogUrls.toArray(URI[]::new));
return (publicId, systemId) -> resolver.resolveEntity(publicId, systemId == null ? "" : systemId);
}
}
并将其放入 jar 文件中以包含在 jaxb2-maven-plugin 的依赖项中。该依赖项将放置在实际的 xjc 依赖项之前,以便此调整后的文件隐藏实际的类。用空字符串替换 null 值的保护措施现在可以避免该问题。
关于Java、xml、目录文件、XSD 模式验证和 NullPointerException (JAXP09020006 : The argument 'systemId' can not be null. ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64538512/