JAX-WS从jar加载WSDL

时间:2009-04-19 03:20:07

标签: wsdl jar jax-ws wsimport

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

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

我已将wsdllocation arg添加到wsimport,以将生成的类指向本地资源。以下代码段是App​​licationService.java中WSDL资源的URL加载。

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

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

这可能吗?还是只是一场疯狂的追逐?

13 个答案:

答案 0 :(得分:48)

是的,这绝对是可能的,因为我在通过javax.xml.ws.EndpointReference(一个与WS-A相关的类)创建客户端时已经这样做了。我已经将WSDL的类路径引用添加到WS-A EndPointReference,并且JAX-WS的Metro实现加载它就好了。无论是从WS-A EndPointReference加载WSDL还是从文件或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服务的QName。现在要记住的重要一点是,您的WSDL需要完整且有效。这意味着如果您的WSDL导入XSD文件或其他WSDL,则URL必须正确。如果将导入的WSDL和XSD包含在与WSDL文件相同的JAR中,则应使用相对URL进行导入,并将所有导入保留在同一JAR文件中。 JAR URL处理程序不会将相对URL视为相对于类路径的相对URL,而是将其视为JAR文件中的相对URL,因此除非您实现自定义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");

答案 1 :(得分:14)

最近的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项目布局src/main/resources/com/adamgent/ws放置WSDL。

确保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应该是微不足道的。

答案 2 :(得分:6)

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

如果Service类中的以下行

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

更改为

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

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

答案 3 :(得分:5)

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

它已针对V2.2修复,因此在编写时只需设置wsdlLocation即可。

答案 4 :(得分:3)

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

答案 5 :(得分:3)

另一个答案是使用

new Service(wsdllocation, servicename );

获取服务对象。

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

答案 6 :(得分:2)

我偶然发现了同样的问题。 JAXWS生成客户端代码使用MyService.class.getResource(".")技巧来加载wsdl文件...但是在测试之后,如果类文件位于filesytem上的目录中,这似乎只能起作用。如果类文件在JAR中,则此调用将为URL返回null。

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

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

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

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

答案 7 :(得分:2)

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

  1. 将WSDL复制到类目录。
  2. 使用classpath替换Service类引用WSDL。
  3. 构建客户端存根。
  4. jar the stubs。
  5. <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 :(得分: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

这很有效,但如果有人能告诉我一个更优雅的解决方案,我会很感激。

答案 9 :(得分:1)

这是一个适合我的方式(特别是通过 http https )。 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获得良好的格式
  • 使用the command line tool生成客户端类:./cxf/bin/wsdl2java -d output/ -client -validate wsdl.xml并导入到项目中

验证WSDL文件中是否存在 http https 的服务定义。在我的情况下,提供商没有 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的标志,我们使用 https http

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 -Dcom.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true -Dcom.sun.xml.internal.ws.transport.http.HttpAdapter.dump=true 。 (不确定原因 - 服务是否使用用于检索WSDL资源的方案?)

就是这样。

要调试连接,请传递:

java.util.logging

命令行上的JVM。这会将http连接代码中的信息写入stdout(遗憾的是不会@register.simple_tag(takes_context=True) def current(context, url_name): print(context) print(context.get('request')) current_path = context.get('request').path return 'active' if current_path.startswith(url_name) else '' >>> [{'True': True, 'False': False, 'None': None}, {'csrf_token': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f61d288b860>, 'links': OrderedDict([('index', 'home'), ('products', 'prodotti'), ('contact_us', 'contattaci')])}, {'forloop': {'counter0': 2, 'revcounter': 1, 'revcounter0': 0, 'counter': 3, 'parentloop': {}, 'last': True, 'first': False}}, {'name': 'contattaci', 'key': 'contact_us'}] >>> None 。请Oracle!)。

答案 10 :(得分:0)

我的解决方案是修改生成的服务。你必须在标题注释中更改 wsdlLocation ,并且instantion块看起来像这样:

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

我将wsdl文件放在ShipService类

旁边的bin目录中

答案 11 :(得分:0)

虽然你可以让它与一些操作一起使用,但我建议这样做,并保持你现在的方式。

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

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

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

答案 12 :(得分:0)

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

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

尝试一下!