【问题标题】:JAX-WS Loading WSDL from jarJAX-WS 从 jar 加载 WSDL
【发布时间】:2010-10-20 08:52:25
【问题描述】:

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

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

我已将 wsdllocation arg 添加到 wsimport 以将生成的类指向本地资源。下面的 sn -p 是从 ApplicationService.java 加载 WSDL 资源的 URL。

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

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

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

【问题讨论】:

    标签: wsdl jar jax-ws wsimport


    【解决方案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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        是的,这绝对是可能的,因为我在通过 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,因此除非您实现自定义 URL 处理程序和您自己的前缀,否则您不能在跨 JAR 运行的 WSDL 中进行导入基于类路径的导入解析。如果您的 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。
        • 转念一想,不,BindingProvider 就在那里,这就是它需要的。只是我的 IDE 喜欢找到奇怪的内部类来使用,而且我喜欢输入奇怪的东西,这让我的 IDE 很难找到它应该找到的类,比如 BindingProvider。
        • 不幸的是,使用 jlink 构建自定义运行时映像时,它在 Java9 上不起作用
        • 对我没用
        【解决方案4】:

        我偶然发现了同样的问题。 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 中,它也可以工作。

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

        【讨论】:

        • 我认为这行不通... getResource 调用会自行发现,因此当您编写 URL 时,您会尝试将 URL 的 WSDL 部分附加到服务的末尾网址的一部分。我认为正确的解决方案是:com.wsdl.MyClass.getResource("MyWebService.wsdl");
        【解决方案5】:

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

        如果服务类中有以下行

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

        改为

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

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

        【讨论】:

        • 几年后我现在正在尝试这个,很抱歉报告它不适合我。 getResource(".") 和 getResource("") 都返回 null
        【解决方案6】:

        另一个答案是使用

        new Service(wsdllocation, servicename );
        

        获取服务对象。

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

        【讨论】:

          【解决方案7】:

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

          1. 将 WSDL 复制到类目录。
          2. 使用类路径替换服务类引用 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>
          

          【讨论】:

            【解决方案8】:

            您描述的是 JAX-WS 中的一个错误:JAX_WS-888 - Wrong code for resolving the URL for a custom wsdlLocation

            它已在 V2.2 中得到修复,所以现在只需设置 wsdlLocation,就像你写的那样,现在应该可以工作了。

            【讨论】:

              【解决方案9】:

              至少对于最近的 JAX-WS,您不需要进行任何模式目录或编程 wsdl 位置设置 IF 您将 WSDL 放入 JAR 中,然后将 wsimport wsdlLocation 设置为相对JAR 中 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 应该很简单。

              【讨论】:

              • 如果此 wsdl 通过相对路径引用其他 xsd,是否也会从 jar 中提取?例如:wsdl 指的是位于 schemaLocation="../../xsds/MyXsd.xsd" 的 xsd
              • &lt;!--- VERY IMPORTANT THAT THE PATH START WITH '/' --&gt; 部分起到了作用。其他答案建议使用 classpath: 前缀路径或使用不带前缀 / 的路径,但这不起作用。
              • 仅在从 1 个 WSDL 文件生成时才有效。如果 包含超过 1 个 它不起作用。
              【解决方案10】:

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

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

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

              【讨论】:

                【解决方案11】:

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

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

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

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

                【讨论】:

                  【解决方案12】:

                  这是对我有用的一个(特别是通过 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 &gt; wsdl.xml 以获得更好的格式
                  • 使用the command line tool:./cxf/bin/wsdl2java -d output/ -client -validate wsdl.xml 生成客户端类并导入到您的项目中

                  验证 httphttps 的服务定义是否存在于 WSDL 文件中。就我而言,供应商没有 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 的类,例如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()
                  

                  另一种方法是在与用于服务调用的连接不同的连接上下载 WSDL(使用 tcpdump -n -nn -s0 -A -i eth0 'tcp port 80' 观察流量)就是这样做:

                  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()
                  

                  请注意,如果wsdlLocationUri 指定了https,这实际上正确地使用了https,尽管wsService 已经用SERVICE_NAME_HTTP 进行了初始化。 (不知道为什么 - 服务使用用于检索 WSDL 资源的方案?)

                  就是这样。

                  为了调试连接,通过:

                  -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,拜托!)。

                  【讨论】:

                    【解决方案13】:

                    无需复杂化任何事情, 只需使用 jar 类加载器

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

                    试试吧!

                    【讨论】:

                    • 尝试了一些更复杂的解决方案,然后找到了这个。 ...像魅力一样工作。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-12-26
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多