Apache CXF创建动态客户端时遭遇ClassCastException问题求助
Apache CXF动态客户端JAXB类加载冲突问题解决
问题背景
基于Spring Boot 2.7 + JDK11构建的应用,使用Apache CXF创建动态WebService客户端,读取WSDL并解析为Java对象。JDK11已移除JAXB库,通过Gradle间接引入了jakarta.xml.bind-api-2.3.3.jar、jaxb-impl-2.3.0.1.jar、jaxb-core-2.3.0.1.jar依赖。
初始异常
使用默认方式创建客户端:
this.wsdlClient = dcf.createClient(this.serviceURL + "?wsdl");
抛出编译异常:
javax.xml.bind.JAXBException: "report.balance" doesnt contain ObjectFactory.class or jaxb.index
自定义类加载器后的异常
尝试指定URLClassLoader加载外部applibs目录下的JAR:
this.wsdlClient = dcf.createClient(this.serviceURL + "?wsdl", new URLClassLoader(new URL[] { new File("applibs\\jakarta.xml.bind-api-2.3.3.jar").toURI().toURL(), new File("applibs\\jaxb-impl-2.3.0.1.jar").toURI().toURL(), new File("applibs\\jaxb-core-2.3.0.1.jar").toURI().toURL(), new File("applibs\\javax.activation-api-1.2.0.jar").toURI().toURL() }));
出现类转换异常:
javax.xml.bind.JAXBException: Provider class com.sun.xml.bind.v2.ContextFactory could not be instantiated: javax.xml.bind.JAXBException: ClassCastException: attempting to cast jar:file:/C:/app/applibs/jakarta.xml.bind-api-2.3.3.jar!/javax/xml/bind/JAXBContext.class to jar:file:/C:/app/build/libs/app.jar!/BOOT-INF/lib/jakarta.xml.bind-api-2.3.3.jar!/javax/xml/bind/JAXBContext.class. Please make sure that you are specifying the proper ClassLoader.
问题根源
- 初始编译异常:CXF动态编译WSDL生成Java类时,JAXB相关类未被纳入编译类路径,导致无法生成JAXB必需的
ObjectFactory等类。 - 类转换异常:自定义URLClassLoader遵循双亲委派机制,会先委托Spring Boot应用类加载器加载
JAXBContext接口,而应用JAR中已存在一份jakarta.xml.bind-api,导致自定义类加载器加载的JAXB实现类,与应用类加载器加载的接口属于不同类实例(类的唯一性由加载器+全类名共同决定),触发类型转换错误。
解决方案
方案1:复用应用类加载器并配置编译类路径(推荐)
无需额外复制JAR,直接让CXF使用应用类加载器,同时确保编译时能访问JAXB依赖:
DynamicClientFactory dcf = DynamicClientFactory.newInstance(); // 设置使用当前线程的应用类加载器 dcf.setClassLoader(Thread.currentThread().getContextClassLoader()); // 将应用类路径传入CXF编译器,确保JAXB依赖在编译时可见 JavaCompiler compiler = dcf.getJavaCompiler(); compiler.getOptions().addClasspath(System.getProperty("java.class.path")); this.wsdlClient = dcf.createClient(this.serviceURL + "?wsdl");
方案2:自定义类加载器修改双亲委派逻辑
如果必须使用外部JAR,重写URLClassLoader优先加载JAXB相关类:
class CustomJaxbClassLoader extends URLClassLoader { public CustomJaxbClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 优先加载JAXB相关类,避免委托父类加载器引入重复类 if (name.startsWith("javax.xml.bind.") || name.startsWith("com.sun.xml.bind.") || name.startsWith("javax.activation.")) { synchronized (getClassLoadingLock(name)) { Class<?> loadedClass = findLoadedClass(name); if (loadedClass == null) { try { loadedClass = findClass(name); } catch (ClassNotFoundException e) { // 本地找不到再委托父类加载 loadedClass = super.loadClass(name, resolve); } } if (resolve) { resolveClass(loadedClass); } return loadedClass; } } // 其他类遵循双亲委派机制 return super.loadClass(name, resolve); } }
使用该类加载器创建客户端:
ClassLoader appClassLoader = Thread.currentThread().getContextClassLoader(); CustomJaxbClassLoader customClassLoader = new CustomJaxbClassLoader(new URL[] { new File("applibs\\jakarta.xml.bind-api-2.3.3.jar").toURI().toURL(), new File("applibs\\jaxb-impl-2.3.0.1.jar").toURI().toURL(), new File("applibs\\jaxb-core-2.3.0.1.jar").toURI().toURL(), new File("applibs\\javax.activation-api-1.2.0.jar").toURI().toURL() }, appClassLoader); this.wsdlClient = dcf.createClient(this.serviceURL + "?wsdl", customClassLoader);
方案3:统一JAXB依赖版本
通过Gradle确保JAXB API与实现版本匹配,避免依赖冲突:
dependencies { implementation 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.3' implementation 'com.sun.xml.bind:jaxb-impl:2.3.7' // 与API版本兼容的最新实现 implementation 'com.sun.xml.bind:jaxb-core:2.3.0.1' implementation 'javax.activation:javax.activation-api:1.2.0' // CXF Spring Boot starter implementation 'org.apache.cxf:cxf-spring-boot-starter-jaxws:3.5.5' }
配置完成后直接使用默认方式创建客户端即可。
关键注意事项
- JDK11中JAXB为可选依赖,必须显式引入,且需保证API与实现版本兼容。
- Spring Boot嵌入式Tomcat使用自定义类加载器
LaunchedURLClassLoader,直接创建URLClassLoader时需处理双亲委派逻辑,避免类重复加载。 - CXF动态编译WSDL时,需确保JAXB相关类在编译类路径中,而非仅运行时类路径。
内容的提问来源于stack exchange,提问作者gburgalum01
相关产品推荐
相关产品推荐

