在 Java 1.6 中为 JAX-WS 提供不同版本的 JAXB

Posted

技术标签:

【中文标题】在 Java 1.6 中为 JAX-WS 提供不同版本的 JAXB【英文标题】:Supplying a different version of JAXB for JAX-WS in Java 1.6 【发布时间】:2012-12-19 04:30:19 【问题描述】:

我有一个附带 jaxb-impl.jar 的第三方 jar,并将其包含在其清单类路径中。问题是,似乎提供您自己的 JAXB 版本(无论它可能是哪个版本)似乎会破坏 JAX-WS 中的 SoapFaultBuilder。

根据Unofficial JAXB Guide的说法,似乎Sun在将JAXB折叠到JDK时故意更改了包名,以避免与单机版本冲突。但是,JDK 附带的 SoapFaultBuilder(我相信是 JAX-WS 的一部分)显式依赖于新的内部包名称。如果您添加了独立的 JAXB jar(即使它与 JAXB 的 number 版本相同),这会导致它在构建错误消息时失败。

这是我的小测试用例:我做了一个微不足道的 Web 服务:

package wstest;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;

//Service Endpoint Interface
@WebService
@SOAPBinding(style = Style.RPC)
public interface HelloWorld

    @WebMethod String getHelloWorldAsString(String name);


还有一个简单地抛出异常的实现。 (由于问题只出现在 SOAPFaultBuilder 中):

package wstest;

import javax.jws.WebService;

//Service Implementation
@WebService(endpointInterface = "wstest.HelloWorld")
public class HelloWorldImpl implements HelloWorld

    @Override
    public String getHelloWorldAsString(String name) 
        //return "Hello World JAX-WS " + name;
        throw new RuntimeException("Exception for: " + name);
    


还有一个发布网络服务的类:

package wstest;

import javax.xml.ws.Endpoint;

//Endpoint publisher
public class HelloWorldPublisher

    public static void main(String[] args) 
       Endpoint.publish("http://localhost:9999/ws/hello", new HelloWorldImpl());
    


我运行 HelloWorldPublisher,然后针对它运行这个客户端:

package wstest;

import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;

public class HelloWorldClient

    public static void main(String[] args) throws Exception 

    URL url = new URL("http://localhost:9999/ws/hello?wsdl");

        //1st argument service URI, refer to wsdl document above
    //2nd argument is service name, refer to wsdl document above
        QName qname = new QName("http://wstest/", "HelloWorldImplService");

        Service service = Service.create(url, qname);

        HelloWorld hello = service.getPort(HelloWorld.class);

        System.out.println(hello.getHelloWorldAsString("Matt"));

    


这会正确地吐出由 Web 服务引发的异常。但是,当我添加任何版本的 jaxb-impl.jar 时,无论是在类路径中还是在认可的库中,我都会得到以下堆栈跟踪:

Exception in thread "main" java.lang.ExceptionInInitializerError
    at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:107)
    at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:78)
    at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:107)
    at $Proxy19.getHelloWorldAsString(Unknown Source)
    at wstest.HelloWorldClient.main(HelloWorldClient.java:21)
Caused by: java.lang.ClassCastException: com.sun.xml.bind.v2.runtime.JAXBContextImpl cannot be cast to com.sun.xml.internal.bind.api.JAXBRIContext
    at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.<clinit>(SOAPFaultBuilder.java:533)
    ... 5 more

发生异常是因为我的 jaxb-impl 中的 com.sun.xml.bind.v2.runtime.JAXBContextImpl 扩展了 com.sun.xml.bind.api.JAXBRIContext 而不是 com.sun.xml.internal.bind.api .JAXBRIContext(注意包层次结构中缺少的“内部”子包)。

还根据Unofficial JAXB Guide,他们说您需要使用认可的库才能正确覆盖 JAXB 的版本。但事实证明,SOAPFaultBuilder 使用 JAXBContext.newInstance() 在类路径中搜索名为 /META-INF/services/javax.xml.bind.JAXBContext 的文件,然后根据文件中指定的类名手动加载(并自反地创建)一个 JAXBContext。所以没关系 - 类路径或认可的 lib 给你同样的行为。

一种解决方法是将-Djavax.xml.bind.JAXBContext=com.sun.xml.internal.bind.v2.ContextFactory 添加到命令行,这会导致JAXBContext.newInstance() 忽略类路径中的/META-INF/services/javax.xml.bind.JAXBContext 文件并手动指定JAXB 的内置版本。另一种解决方法是简单地不指定您自己的 JAXB 并使用内置于 JDK 中的版本,但从非官方 JAXB 指南看来,Sun 设计此系统是为了能够处理提供您自己的 JAXB 实现。有没有人能够成功地提供一个 JAXB 版本并且仍然能够成功地捕获故障消息? (只要 Web 服务没有产生任何故障,对我来说一切都运行良好)。

【问题讨论】:

【参考方案1】:

我被困在这个问题上,但能够通过设置如下系统属性来使用此论坛问答中列出的“解决方法”:

System.setProperty("javax.xml.bind.JAXBContext", 
                   "com.sun.xml.internal.bind.v2.ContextFactory"); 

【讨论】:

【参考方案2】:

这个问题的症结在于 JAXB API 发生了变化,您尝试使用的运行时实现与 JDK 捆绑的 JAXB API 的版本不匹配。

为了使用不同的版本,您应该将jaxb-api.jar 和jaxws-api.jar 的对应版本复制到认可的库中(例如%JAVA_HOME%\lib\endorsed)。

选项的完整列表在7.1.2 of the Unofficial JAXB Guide部分给出

将实现 jars(例如 jaxb-impl.jar)复制到认可的 lib 中是错误的,这些应该只是在您的类路径中。

另外请注意,如果您尝试使用更新版本的 jaxb 而不包括兼容版本的 jaxws,则可能会遇到麻烦。这是因为旧的 jaxws 试图引用旧的 jaxb,所以如果您要更改一个,请确保两者都做。 (com.sun.xml.internal.ws 包中的堆栈跟踪暗示了旧的 jax-ws 实现。即使是最新版本的 Java 仍然附带旧版本 1 jaxb 和 jaxws api)。

【讨论】:

感谢您的回复。它有一些很好的信息,但我认为它并不完全正确。看来您确实必须同时覆盖 JAXB 和 JAX-WS。但它似乎与版本冲突没有任何关系,因为(正如我在问题中提到的)即使您尝试提供与 JDK 提供的版本匹配的自己的 JAXB jar,也会出现问题。 internal 包名称不旧。 Sun 引入它是为了避免与用户提供的 JAXB 发生冲突。 (参见 JAXB 非官方指南介绍的第二个要点。)Java 1.7 u17 仍然有这个内部包名称。 com.sun.xml.internal.ws 是旧的 JAXWS 版本,即使在最新的 Java 版本中也没有更新。 我已经成功地从您的示例中重现了您的问题,并且还通过使用最新版本的 jaxws(将 jaxws-api.jar 添加到 lib/endorsed)来修复它。为了清楚起见,我还更新了我的答案。如果您仍有问题,请告诉我,我可以通过 github 或类似方式共享我的 Eclipse 工作区。 我想我错过的主要事情是,如果它确实是新旧版本冲突,那么为什么我在用内置版本 2.1.10 替换时会出现异常单机版 2.1.10? 你是对的,问题不一定是新的与旧的,而是独立的与内置的。您收到错误是因为您没有完全替换内置的。为此,您必须让 JVM 引导加载程序在 rt.jar 之前加载新的 api jar【参考方案3】:

API 文档中描述了另一种无需修改系统属性的可能解决方案

https://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/1.6/api/javax/xml/bind/JAXBContext.html

您可以在模型类的包中放置一个 jaxb.properties 文件。

javax.xml.bind.context.factory=com.sun.xml.internal.bind.v2.ContextFactory

【讨论】:

以上是关于在 Java 1.6 中为 JAX-WS 提供不同版本的 JAXB的主要内容,如果未能解决你的问题,请参考以下文章

Java Paypal 集成 SOAP JAX-WS - SSL 握手异常

JAX-WS服务端及客户端

如何使用JAX-WS 2.0模拟soap响应?

WebService:JAX-WS实现WebService

在 Java 中提供 RESTful JSON API

使用内置 Java JAX-WS Web 服务器发布多个端点