问题
环境: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>
问题解决。