一个XML解析错误的排查

更新:
JAXB其实是双亲委派模型被破坏的一个例子,加载SPI。涉及到一个线程上下文类加载器(Thread Context ClassLoader)。
记得之前的这句代码吗?final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);,跳入load方法看下:

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

这里的service参数就是SAXParserFactorySAXB解析器的工厂类。而返回的正是线程上下文类加载器,此时就是这个类加载器去加载SPI代码类,不是jdk里面的类了。所以,出现了这个问题。


项目里面需要用到XML解析,我权衡之后使用了JAXB,因为:1. 对接系统返回的XML比较复杂,如果使用时候使用DOM4J之类的,代码与XML格式强耦合,所以使用JAXB,直接使用注解映射XML和JavaBean;2. JDK原生支持的。
但是烦恼就此开始,XML和JavaBean转换时倒是容易:

    public static <T> T convertXmlToJavaBean(Class<T> clazz, String xmlString) throws Exception {
        try(StringReader stringReader = new StringReader(xmlString)) {
            return JAXB.unmarshal(stringReader, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("XML 格式错误!");
        }
    }

    public static <T> String convertJavabeanToXml(T bean) throws Exception {
        try(StringWriter stringWriter = new StringWriter()) {
            JAXB.marshal(bean, stringWriter);
            return stringWriter.toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception(e.getMessage());
        }

    }

不过,根据每个接口的XML格式写JavaBean类把我累够呛。好在最后还是写完了。
JAXB相关的注解参考这里,比较详细,JAXB常用注解讲解(超详细)。
这些倒还好说,就是体力活,不过最后测试时候出现了这个错误。
Feature 'http://javax.xml.XMLConstants/feature/secure-processing' is not recognized.
队列,忘记说了,项目是Maven项目,且是我接手别人的项目,项目依赖了不少包。我在另一个非Maven项目里面执行时候是没有问题,所以显然是执行环境的问题。
Debug进去,发现了报错的位置:
com.sun.xml.internal.bind.v2.util.XmlFactory.createParserFactory()
factory.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", !isXMLSecurityDisabled(disableSecureProcessing));
正是此句代码。SAXParserFactory是个抽象工厂类,由newInstance提供实现类。

    public static SAXParserFactory newInstance() {
        return FactoryFinder.find(
                /* The default property name according to the JAXP spec */
                SAXParserFactory.class,
                /* The fallback implementation class name */
                "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
    }

find方法比较长,本次错误发生时候工厂实现类是由这句提供:

T provider = findServiceProvider(type);
if (provider != null) {
   return provider;
}

所以直接进到这个方法里面

    /*
     * Try to find provider using the ServiceLoader API
     *
     * @param type Base class / Service interface  of the factory to find.
     *
     * @return instance of provider class if found or null
     */
    private static <T> T findServiceProvider(final Class<T> type) {
        try {
            return AccessController.doPrivileged(new PrivilegedAction<T>() {
                public T run() {
                    final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
                    final Iterator<T> iterator = serviceLoader.iterator();
                    if (iterator.hasNext()) {
                        return iterator.next();
                    } else {
                        return null;
                    }
                 }
            });
        } catch(ServiceConfigurationError e) {
            // It is not possible to wrap an error directly in
            // FactoryConfigurationError - so we need to wrap the
            // ServiceConfigurationError in a RuntimeException.
            // The alternative would be to modify the logic in
            // FactoryConfigurationError to allow setting a
            // Throwable as the cause, but that could cause
            // compatibility issues down the road.
            final RuntimeException x = new RuntimeException(
                    "Provider for " + type + " cannot be created", e);
            final FactoryConfigurationError error =
                    new FactoryConfigurationError(x, x.getMessage());
            throw error;
        }
    }

这个方法就是在当前线程的ContextClassLoader路径上找需要的类。

而在执行成功的环境上执行的这句的结果是:
T provider = findServiceProvider(type);null,所以进行到这句:
return newInstance(type, fallbackClassName, null, true);

也就是说,区别在于findServiceProvider(type)方法的执行结果,也就是由ServiceLoader.load(type)执行结果决定。
就是与类加载有关系。
翻看小项目依赖,找到了一个叫XOM:1.0的解析包,这个包依赖于xercesImpl:2.6.2ServiceLoader.load(type)就是就是加载到了这个包的类,所以解决办法就是去掉这个依赖,或者是和我一样升级到了XOM:1.3.2xercesImpl对应升级到了xercesImpl:2.8.0,问题便解决了。
问题现在是解决了,但是我有个疑问,根据双亲委派机制,应该会先到JDK路径找的吧,为什么会先加载扩展包的实现类呢?我对双亲委派模型本来就有点迷,更加迷了,改日细细研究一番。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值