JAX-WS 从 jar 文件加载 WSDL

46

我正在编写一个厚客户端,其中使用了SOAP服务来实现一些功能(例如缺陷报告等)。

我已经成功使用JAX-WS,但默认情况下(至少在netbeans中),它每次初始化服务时都会从远程服务器获取WSDL。 我认为这有助于提供某些版本支持等,但这不是我想要的。

我已经添加了wsdllocation参数到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 异常。

这是否可能?还是只是一场徒劳的追逐?

13个回答

51

我肯定可以做到这一点,因为我在使用javax.xml.ws.EndpointReference创建客户端时已经实现过。通过将类路径引用添加到WS-A EndPointReference的WSDL中,JAX-WS的Metro实现可以很好地加载它。无论是从WS-A EndPointReference还是从文件或http URL中加载WSDL,你的JAX-WS实现都应该使用相同的WSDL解析代码,因为你所做的只是解决URL。

对于你来说,最好的方法可能是像以下这样做:

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

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

在您的WSDL中,<wsdl:service name="...">代表WSDL中的一个服务QName。需要记住的重要一点是,您的WSDL必须完整且有效。这意味着如果您的WSDL导入XSD文件或其他WSDL,那么URL必须是正确的。如果您将导入的WSDL和XSD文件与WSDL文件放在同一个JAR文件中,则应使用相对URL进行导入,并将所有导入项保留在同一JAR文件中。 JAR URL处理程序不会将相对URL视为与类路径相关而是视为相对于JAR文件内部,因此,除非实现自定义URL处理程序和自己的前缀以基于类路径解析导入,否则您不能在WSDL中跨JAR运行导入。如果您的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");

对我来说,似乎需要使用WSBindingProvider而不是BindingProvider。 - skiphoppy
转念一想,不,BindingProvider就在那里,而且那就是它需要的。只是我的IDE喜欢找到奇怪的内部类来使用,而我喜欢输入奇怪的东西,让我的IDE难以找到应该找到的类,比如BindingProvider。 - skiphoppy
不幸的是,在使用jlink构建自定义运行时映像时,它无法在Java9上工作。 - madduci
没有对我起作用。 - Rafael Lima

17

对于最近的JAX-WS,如果您将WSDL放在JAR中并将wsimport wsdlLocation设置为JAR中WSDL的相对资源路径,则无需执行任何模式目录或编程式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>

对于上面的示例,您需要将WSDL文件放置在Maven项目布局中的此处: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应该很容易。


如果这个WSDL文件通过相对路径引用其他XSD文件,那么它们也会从JAR包中获取吗?例如:WSDL文件通过schemaLocation="../../xsds/MyXsd.xsd"引用了一个XSD文件。 - coderplus
4
“<!--- VERY IMPORTANT THAT THE PATH START WITH '/' -->” 这一部分是解决问题的关键。其他答案建议在路径前加上“classpath:”,或者不使用前缀“/”,但这并没有起作用。 - Martin Devillers
<wsdlLocaltion>只在从一个WSDL文件生成时起作用。 如果<wsdlFiles>包含多个<wsdlFile>,它将不起作用。 - user1323562

6
也许有点晚了,但我找到了一个相当简单的解决方案,可以解决这个问题,但这需要改变Service类生成的代码:
如果在Service类中出现以下行:
baseUrl = net.example.ApplicationService.class.getResource(".");

被改为

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

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


我现在尝试了几年,很抱歉地报告说这对我不起作用。getResource(".")和getResource("")都返回null。 - IcedDante

5

4
如果您的类路径中包含“.”,则Class.getResource(".")将返回您执行java命令的目录的URL。否则,它将返回null。相应地调整wsdllocation。

3
另一种解决方案是使用HTML标签。
new Service(wsdllocation, servicename );

获取服务对象。

这是我解决问题的方法。


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文件中,那么它也能正常工作。

我猜大多数人实际上会将其打包在一个jar文件中!


我认为这不会起作用... getResource 调用会找到自身,因此当你组合 URL 时,你正在尝试将 URL 的 WSDL 部分附加到 URL 的 Service 部分的末尾。我认为正确的解决方案是:com.wsdl.MyClass.getResource("MyWebService.wsdl"); - IcedDante

2

在构建客户端jar之前,我替换了WSDL位置,以下是具体步骤:

  1. 将WSDL复制到classes目录下。
  2. 使用classpath替换Service类引用WSDL。
  3. 构建客户端存根。
  4. 将存根打包成jar文件。
<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>

2
不需要把任何事情复杂化,只需使用jar类加载器。
ClassLoader cl = SomeServiceImplService.class.getClassLoader();
SERVICE_WSDL_LOCATION = cl.getResource("META-INF/wsdls/service.wsdl");

试一试!


尝试了几个更复杂的解决方案,然后找到了这个......运行得非常好。 - sympatric greg

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

这个方法可以工作,但如果有人能展示一个更优雅的解决方案,我会非常感激。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接