OSGi跨bundle调用,jaxb-impl异常

2024-01-25 11:24:35 浏览数 (3)

问题

环境:JDK11

为什么是JDK11,由于jaxb是作为JDK8的一部分,在JDK11中已经被剥离出来需要单独引入。

项目中使用osgi架构,在处理xml解析的实现中使用了jdk自带的Javax.xml.bind包。在单模块结构工程中都没有问题,但是引到插件化模式工程结构中,会提示找不到JaxbContext的工厂类而报错。具体报错信息如下:

代码语言:javascript复制
Caused by: javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:278) ~[na:na]
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:421) ~[na:na]
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:721) ~[na:na]
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:662) ~[na:na]
    at com.ruijie.pctool.plugins.device.pojo.bo.PluginLoaderExt.fromXml(PluginLoaderExt.java:79) ~[na:na]
    ... 32 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactory
    at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:476) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) ~[na:na]
    at org.eclipse.osgi.internal.framework.ContextFinder.loadClass(ContextFinder.java:135) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) ~[na:na]
    at javax.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:122) ~[na:na]
    at javax.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:155) ~[na:na]
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:276) ~[na:na]
    ... 36 common frames omitted
JavaFX Application Thread

导入的依赖:(这里shade包是继承了jaxb的api,impl,runtime等包的合集)

代码语言:javascript复制
<dependency>
    <groupId>com.ruijie.osgi.thirdparty</groupId>
    <artifactId>javax.xml.bind.shade</artifactId>
    <version>2.3.1.v202203241600</version>
</dependency>

原因分析

在引入的包中,所提供的工厂类为com.sun.xml.bind.v2.ContextFactory,并不是错误提示的com.sun.xml.internal.bind.v2.ContextFactory;

其实jaxb提供了几种构建jaxbcontext的方式,如果都匹配不到,那么会使用系统默认指定的com.sun.xml.internal.bind.v2.ContextFactory来构建,但是不知道为什么指定了一个jar不存在的包路径:

代码语言:javascript复制
javax.xml.bind.ContextFinder#find(java.lang.String, java.lang.String, java.lang.ClassLoader, java.util.Map)

JaxbContext源码

代码语言:javascript复制
static JAXBContext find(String factoryId, String contextPath, ClassLoader classLoader, Map properties) throws JAXBException {
        if (contextPath != null && !contextPath.isEmpty()) {
            Class[] contextPathClasses = ModuleUtil.getClassesFromContextPath(contextPath, classLoader);
            String factoryClassName = jaxbProperties(contextPath, classLoader, factoryId);
            if (factoryClassName == null && contextPathClasses != null) {
                factoryClassName = jaxbProperties(contextPathClasses, factoryId);
            }
 
            if (factoryClassName != null) {
                return newInstance(contextPath, contextPathClasses, factoryClassName, classLoader, properties);
            } else {
                String factoryName = classNameFromSystemProperties();
                if (factoryName != null) {
                    return newInstance(contextPath, contextPathClasses, factoryName, classLoader, properties);
                } else {
                    JAXBContextFactory obj = (JAXBContextFactory)ServiceLoaderUtil.firstByServiceLoader(JAXBContextFactory.class, logger, EXCEPTION_HANDLER);
                    if (obj != null) {
                        ModuleUtil.delegateAddOpensToImplModule(contextPathClasses, obj.getClass());
                        return obj.createContext(contextPath, classLoader, properties);
                    } else {
                        factoryName = firstByServiceLoaderDeprecated(JAXBContext.class, classLoader);
                        if (factoryName != null) {
                            return newInstance(contextPath, contextPathClasses, factoryName, classLoader, properties);
                        } else {
                            Class ctxFactory = (Class)ServiceLoaderUtil.lookupUsingOSGiServiceLoader("javax.xml.bind.JAXBContext", logger);
                            if (ctxFactory != null) {
                                return newInstance(contextPath, contextPathClasses, ctxFactory, classLoader, properties);
                            } else {
                                logger.fine("Trying to create the platform default provider");
                                return newInstance(contextPath, contextPathClasses, ModuleUtil.DEFAULT_FACTORY_CLASS, classLoader, properties);
                            }
                        }
                    }
                }
            }
        } else {
            throw new JAXBException(Messages.format("ContextFinder.NoPackageInContextPath"));
        }
    }

1、首先,寻找系统是否自定义jaxb.properties,指明所需的工厂类全限定名

2、其次,寻找系统是否配置了系统变量javax.xml.bind.JAXBContextFactory来指定工厂对象

3、接着,寻找jaxb包提供的META-INF/servies/javax.xml.bind.JAXBContextFactory配置文件,里面配置了所需的工厂类全限定名

4、若还是没有,lookuposgibundle寻找

5、若还是没有,就构建系统默认指定的com.sun.xml.internal.bind.v2.ContextFactory;由于该路径下不存在这个类,所以直接报错

按理,jaxb包提供的META-INF/servies/javax.xml.bind.JAXBContextFactory这个文件存在的情况下,为什么还是没被识别到呢?猜测由于OSGi的隔离机制,在跨bundle使用过程中读取不到该配置文件。

解决

由于jaxb-api提供了SPI的扩展机制,基于他的实现很多,所以集成了如下的依赖解决该问题:

代码语言:javascript复制
<!-- jaxb-api实现需要的依赖 -->
<dependency>
    <groupId>com.ruijie.osgi.thirdparty</groupId>
    <artifactId>javax.karta.xml.bind-api</artifactId>
    <version>2.3.3.v20220331-2000</version>
</dependency>
<dependency>
    <groupId>com.ruijie.osgi.thirdparty</groupId>
    <artifactId>javax.karta.activation</artifactId>
    <version>1.2.2.v20220331-2000</version>
</dependency>
<dependency>
    <groupId>com.ruijie.osgi.thirdparty</groupId>
    <artifactId>com.sun.xml.bind.jaxb-osgi</artifactId>
    <version>2.3.6.v2022331-2000</version>
</dependency>
<dependency>
    <groupId>com.ruijie.osgi.thirdparty</groupId>
    <artifactId>org.glassfish.osgi-resource-locator</artifactId>
    <version>2.4.0.v20220331-2000</version>
</dependency>

问题解决。

0 人点赞