46

我正在编写一个胖客户端,它使用 SOAP 服务来实现某些功能(错误报告等)

我的 JAX-WS 工作正常,但默认情况下(至少在 netbeans 中)它每次初始化服务时都会从远程服务器获取 WSDL。我希望这有助于提供一些版本控制支持等,但这不是我想要的。

我已将wsdllocationarg 添加到 wsimport 以将生成的类指向本地资源。以下片段是从 ApplicationService.java 加载 WSDL 资源的 URL。

baseUrl = net.example.ApplicationService.class.getResource(".");
url = new URL(baseUrl, "service.wsdl");

我很确定指向存储在 net/example/resources 包中的 jar 中的资源应该没有问题,并且 jar 本身是按预期构造的。但是该服务不会加载...具体来说,当我调用 ApplicationService.getPort(); 时,我得到一个 NullPointerException;

这可能吗?还是只是一场野鹅追逐?

4

13 回答 13

50

是的,这绝对是可能的,因为我在通过 javax.xml.ws.EndpointReference(一个 WS-A 相关类)创建客户端时已经做到了。我已经向 WS-A EndPointReference 添加了对 WSDL 的类路径引用,并且 JAX-WS 的 Metro 实现很好地加载了它。无论是从 WS-A EndPointReference 还是从文件或 http URL 加载 WSDL,您的 JAX-WS 实现都应该使用与解析 URL 相同的 WSDL 解析代码。

对您来说最好的方法可能是执行以下操作:

URL wsdlUrl = MyClass.class.getResource(
            "/class/path/to/wsdl/yourWSDL.wsdl");

Service yourService= Service.create(
            wsdlUrl,
            ...);

其中 ... 表示 WSDL 内的 WSDL 服务的 QName。现在要记住的重要一点是您的 WSDL 需要完整且有效。这意味着如果您的 WSDL 导入 XSD 文件或其他 WSDL,则 URL 必须正确。如果您将导入的 WSDL 和 XSD 包含在与 WSDL 文件相同的 JAR 中,则应该使用相对 URL 进行导入并将所有导入保存在同一个 JAR 文件中。JAR URL 处理程序不会将相对 URL 视为相对于类路径,而是将相对于 JAR 文件中的相对 URL,因此您不能在跨 JAR 运行的 WSDL 中导入导入,除非您实现自定义 URL 处理程序和您自己的前缀来做基于类路径的导入解析。如果您的 WSDL 导入外部资源,那没关系,但是,如果这些资源移动了,您就会为自己注册维护问题。即使从您的类路径中使用 WSDL 的静态副本也违背了 WSDL、Web 服务和 JAX-WS 的精神,但有时它是必要的。

最后,如果您嵌入了静态 WSDL,我建议您至少使服务端点可配置以用于测试和部署目的。重新配置 Web 服务客户端端点的代码如下:

  YourClientInterface client = yourService.getPort(
            new QName("...", "..."),
            YourClientInterface.class);
  BindingProvider bp = (BindingProvider) client;
  bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                "http://localhost:8080/yourServiceEndpoint");
于 2009-05-14T14:14:05.930 回答
17

至少对于最近的 JAX-WS,如果您将 WSDL 放在 JAR 中,然后将 wsimport 设置为 JAR 中wsdlLocationWSDL 的相对资源路径,则不需要执行任何模式目录或编程 wsdl 位置设置。那就是 JAX-WS 使用 Java 的内置函数Class.getResource来加载 WSDL。

如果您使用 Maven,它类似于:

  <plugin>
    <groupId>org.jvnet.jax-ws-commons</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>2.3</version>
    <executions>
      <execution>
        <goals>
          <goal>wsimport</goal>
        </goals>
        <!-- Following configuration will invoke wsimport once for each wsdl. -->
        <configuration>
            <!--- VERY IMPORTANT THAT THE PATH START WITH '/' -->
    <wsdlLocation>/com/adamgent/ws/blah.wsdl</wsdlLocation>
    <wsdlDirectory>${basedir}/src/main/resources/com/adamgent/ws</wsdlDirectory>
    <wsdlFiles><wsdlFile>blah.wsdl</wsdlFile></wsdlFiles>
       </configuration>
      </execution>
    </executions>
  </plugin>

对于上面的示例,您可以将使用 Maven 项目布局的 WSDL 放在此处src/main/resources/com/adamgent/ws

确保 WSDL 进入 Maven 的 JAR,例如:

<build>
      <resources>
        <resource>
          <directory>src/main/resources</directory>
        </resource>
      </resources> ....

现在您的 wsimport 生成的代码和 WSDL 都在一个自包含的 JAR 中。要使用该服务,您不必设置 WSDL 位置,并且非常简单:

BlahService myService = new BlayService_Service().getBlahServicePort();

将它映射到 ANT 的 wsimport 应该很简单。

于 2013-08-19T22:01:29.257 回答
6

也许有点晚了,但我找到了一个非常简单的解决方案来解决这个问题,但这涉及到 Service 类的生成代码的更改:

如果服务类中的以下行

baseUrl = net.example.ApplicationService.class.getResource(".");

改为

baseUrl = net.example.ApplicationService.class.getResource("");

即使使用打包在 JAR 中的 WSDL,它也能正常工作。不确定在这两种情况下 getResource() 的确切假定行为,但到目前为止,在多个操作系统和 Java 版本上,我没有遇到这种方法的任何问题。

于 2009-11-20T14:40:29.240 回答
5

您描述的是 JAX-WS 中的一个错误:JAX_WS-888 - 用于解析自定义 wsdlLocation 的 URL 的错误代码

它已针对 V2.2 进行了修复,因此wsdlLocation您编写的设置现在应该可以工作了。

于 2013-04-09T21:57:30.537 回答
4

如果你的类路径有“。” 在其中,Class.getResource(".") 将返回您执行 java 命令的目录的 URL。否则,它将返回一个空值。相应地调整 wsdllocation。

于 2009-05-06T17:40:14.750 回答
3

另一个答案是使用

new Service(wsdllocation, servicename );

获取服务对象。

这就是我解决问题的方法。

于 2010-01-08T07:11:06.753 回答
2

我偶然发现了同样的问题。JAXWS 生成客户端代码使用该MyService.class.getResource(".")技巧来加载 wsdl 文件......但经过测试,这似乎只有在类文件位于文件系统上的目录中时才有效。如果类文件在 JAR 中,则此调用为 URL 返回 null。

这听起来像是 JDK 中的一个错误,因为如果您像这样构建 URL:

final URL url = new URL( MyService.class.getResource( MyService.class.getSimpleName() + ".class"), "myservice.wsdl");

如果类和 wsdl 捆绑在一个 jar 中,它也可以工作。

我想大多数人实际上会捆绑在一个罐子里!

于 2009-06-09T14:35:05.967 回答
2

我在构建客户端 jar 之前替换了 WSDL 位置。

  1. 将 WSDL 复制到类目录。
  2. 使用类路径替换服务类引用 WSDL。
  3. 构建客户端存根。
  4. 罐子里的存根。
<copy todir="@{dest-dir}/@{dir-package}" verbose="@{verbose}">
  <fileset dir="@{dir-wsdl}" includes="*.wsdl,*.xsd" />
</copy>
<echo message="Replacing Service to point to correct WSDL path..." />
<replaceregexp match="new URL(.*)" replace='Class.class.getResource("@{name-wsdl}");' flags="gm">
  <fileset dir="@{source-dest-dir}">
    <include name="@{dir-package}/*Service.java" />
  </fileset>
</replaceregexp>
<replaceregexp match="catch (.*)" replace='catch (Exception ex) {' flags="gm">
  <fileset dir="@{source-dest-dir}">
    <include name="@{dir-package}/*Service.java" />
  </fileset>
</replaceregexp>
于 2011-06-24T09:22:00.353 回答
2

无需复杂化,只需使用 jar 类加载器

ClassLoader cl = SomeServiceImplService.class.getClassLoader();
SERVICE_WSDL_LOCATION = cl.getResource("META-INF/wsdls/service.wsdl");

试试看!

于 2018-11-29T14:27:32.403 回答
1

这是我的 hack-y 解决方法。

我从 jar 中解压 WSDL 并将其写入 jar 附近的文件中:

File wsdl = new File("../lib/service.wsdl");
InputStream source = getClass().getResource("resources/service.wsdl").openStream();
FileOutputStream out = new FileOutputStream(wsdl);

byte[] buffer = new byte[512];
int read;
while((read = source.read(buffer)) >= 0) {
    out.write(buffer, 0, read);
}

然后将服务类指向file:../lib/service.wsdl.

这可行,但如果有人能向我展示一个更优雅的解决方案,我将不胜感激。

于 2009-04-19T15:08:31.367 回答
1

这是对我有用的一个(特别是通过httphttps)。Oracle JDK 1.8.0_51 的 JAX-WS 使用Apache CXF 3.1.1 创建的类的案例。

请注意,在任何情况下,仅在第一次调用时才获得远程 WSDL。根据使用模式(长时间运行的程序),这可能是完全可以接受的。

基础知识:

  • 从远程主机下载 WSDL 并存储为文件:wget --output-document=wsdl_raw.xml $WSDL_URL
  • 你可能想要xmllint --format wsdl_raw.xml > wsdl.xml漂亮的格式
  • 使用命令行工具生成客户端类:./cxf/bin/wsdl2java -d output/ -client -validate wsdl.xml并导入您的项目

验证WSDL 文件中是否存在httphttps的服务定义。就我而言,提供者没有用于https(但确实接受https流量),我不得不手动添加它。WSDL 中应包含以下内容:

  <wsdl:service name="fooservice">
    <wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort">
      <soap:address location="http://ws.example.com/a/b/FooBarWebService"/>
    </wsdl:port>
  </wsdl:service>
  <wsdl:service name="fooservice-secured">
    <wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort">
      <soap:address location="https://ws.example.com/a/b/FooBarWebService"/>
    </wsdl:port>
  </wsdl:service>

CXF 应该已经生成了一个实现javax.xml.ws.Service名为 example的类Fooservice,并带有适当的构造函数:

public class Fooservice extends Service {

  public Fooservice(URL wsdlLocation) {
      super(wsdlLocation, SERVICE);
  }

  public Fooservice(URL wsdlLocation, QName serviceName) {
      super(wsdlLocation, serviceName);
  }

  public Fooservice() {
      super(WSDL_LOCATION, SERVICE);
  }

  ...etc...

在您的代码中的某处(这里是一些 Groovy 以便于阅读),您初始化上述Service实例,然后调用一个端口。在这里,根据调用的标志secure,我们使用httpshttp

static final String NAMESPACE = 'com.example.ws.a.b'
static final QName SERVICE_NAME_HTTP = new QName(NAMESPACE, 'fooservice')
static final QName SERVICE_NAME_HTTPS = new QName(NAMESPACE, 'fooservice-secured')

Fooservice wsService
File wsdlfile = new File('/somewhere/on/disk/wsdl.xml')

// If the file is missing there will be an exception at connect
// time from sun.net.www.protocol.file.FileURLConnection.connect
// It should be possible to denote a resource on the classpath 
// instead of a file-on-disk. Not sure how, maybe by adding a handler
// for a 'resource:' URL scheme?

URI wsdlLocationUri = java.nio.file.Paths(wsdlfile.getCanonicalPath()).toUri()

if (secure) {
  wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTPS)
}
else {
  wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP)
}

SomeServicePort port = wsService.getSomeServicePort()

port.doStuff()

另一种方法是在与用于服务调用(用于tcpdump -n -nn -s0 -A -i eth0 'tcp port 80'观察流量)的连接不同的连接上下载 WSDL,只需执行以下操作:

URI wsdlLocationUri

if (secure) {
   wsdlLocationUri = new URI('https://ws.example.com/a/b/FooBarWebService?wsdl')
}
else {
   wsdlLocationUri = new URI('http://ws.example.com/a/b/FooBarWebService?wsdl')
}

Fooservice wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP)

SomeServicePort port = wsService.getSomeServicePort()

port.doStuff()

请注意,如果指定https ,这实际上正确使用https,尽管事实上已经使用. (不知道为什么 - 服务是否使用用于检索 WSDL 资源的方案?)wsdlLocationUriwsServiceSERVICE_NAME_HTTP

就是这样。

要调试连接,请通过:

-Dcom.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true
-Dcom.sun.xml.internal.ws.transport.http.HttpAdapter.dump=true

在命令行上到 JVM。这会将来自 http 连接代码的信息写入标准输出(不幸的是,不是java.util.logging。Oracle,拜托!)。

于 2015-09-02T16:09:56.170 回答
0

我的解决方案是修改生成的服务。您必须更改标头注释中的wsdlLocation并且实例块如下所示:

    static {
    URL url = null;
    url = com.ups.wsdl.xoltws.ship.v1.ShipService.class.getResource("Ship.wsdl");
    SHIPSERVICE_WSDL_LOCATION = url;
    }

我将 wsdl 文件放在 ShipService 类旁边的 bin 目录中

于 2014-05-22T21:21:21.053 回答
0

虽然你可以通过一些操作让它工作,但我建议要这样做并保持你现在的方式。

Web 服务端点提供者应提供 WSDL 作为其合同的一部分。您生成的代码应该从服务器本身的 WSDL 中提取。

在 WebSphere 上部署时,您可以从部署 UI 将端点更改为其他端点。其他应用程序服务器您可能需要找出供应商特定的绑定 XML 来执行此操作。

它只发生在初始化时,因此对整个应用程序的影响应该可以忽略不计。

于 2015-08-24T19:53:42.563 回答