JAX-WS 客户端:访问本地 WSDL 的正确路径是啥?

Posted

技术标签:

【中文标题】JAX-WS 客户端:访问本地 WSDL 的正确路径是啥?【英文标题】:JAX-WS client : what's the correct path to access the local WSDL?JAX-WS 客户端:访问本地 WSDL 的正确路径是什么? 【发布时间】:2011-05-08 23:51:36 【问题描述】:

问题是我需要从提供的文件中构建 Web 服务客户端。我已将此文件存储在本地文件系统上,并且虽然我将 WSDL 文件保存在正确的文件系统文件夹中,但一切都很好。当我将它部署到服务器或从文件系统文件夹中删除 WSDL 时,代理找不到 WSDL 并出现错误。我在网上搜索了以下帖子,但我无法使其工作:JAX-WS Loading WSDL from jarhttp://www.java.net/forum/topic/glassfish/metro-and-jaxb/client-jar-cant-find-local-wsdl-0http://blog.vinodsingh.com/2008/12/locally-packaged-wsdl.html

我使用的是 NetBeans 6.1(这是一个遗留应用程序,我必须使用这个新的 Web 服务客户端进行更新)。下面是 JAX-WS 代理类:

    @WebServiceClient(name = "SOAService", targetNamespace = "http://soaservice.eci.ibm.com/", wsdlLocation = "file:/C:/local/path/to/wsdl/SOAService.wsdl")
public class SOAService
    extends Service


    private final static URL SOASERVICE_WSDL_LOCATION;
    private final static Logger logger = Logger.getLogger(com.ibm.eci.soaservice.SOAService.class.getName());

    static 
        URL url = null;
        try 
            URL baseUrl;
            baseUrl = com.ibm.eci.soaservice.SOAService.class.getResource(".");
            url = new URL(baseUrl, "file:/C:/local/path/to/wsdl/SOAService.wsdl");
         catch (MalformedURLException e) 
            logger.warning("Failed to create URL for the wsdl Location: 'file:/C:/local/path/to/wsdl/SOAService.wsdl', retrying as a local file");
            logger.warning(e.getMessage());
        
        SOASERVICE_WSDL_LOCATION = url;
    

    public SOAService(URL wsdlLocation, QName serviceName) 
        super(wsdlLocation, serviceName);
    

    public SOAService() 
        super(SOASERVICE_WSDL_LOCATION, new QName("http://soaservice.eci.ibm.com/", "SOAService"));
    

    /**
     * @return
     *     returns SOAServiceSoap
     */
    @WebEndpoint(name = "SOAServiceSOAP")
    public SOAServiceSoap getSOAServiceSOAP() 
        return super.getPort(new QName("http://soaservice.eci.ibm.com/", "SOAServiceSOAP"), SOAServiceSoap.class);
    

    /**
     * @param features
     *     A list of @link javax.xml.ws.WebServiceFeature to configure on the proxy.  Supported features not in the <code>features</code> parameter will have their default values.
     * @return
     *     returns SOAServiceSoap
     */
    @WebEndpoint(name = "SOAServiceSOAP")
    public SOAServiceSoap getSOAServiceSOAP(WebServiceFeature... features) 
        return super.getPort(new QName("http://soaservice.eci.ibm.com/", "SOAServiceSOAP"), SOAServiceSoap.class, features);
    


这是我使用代理的代码:

   WebServiceClient annotation = SOAService.class.getAnnotation(WebServiceClient.class);
   // trying to replicate proxy settings
   URL baseUrl = com.ibm.eci.soaservice.SOAService.class.getResource("");//note : proxy uses "."
   URL url = new URL(baseUrl, "/WEB-INF/wsdl/client/SOAService.wsdl");
   //URL wsdlUrl = this.getClass().getResource("/META-INF/wsdl/SOAService.wsdl"); 
   SOAService serviceObj = new SOAService(url, new QName(annotation.targetNamespace(), annotation.name()));
   proxy = serviceObj.getSOAServiceSOAP();
   /* baseUrl;

   //classes\com\ibm\eci\soaservice
   //URL url = new URL(baseUrl, "../../../../wsdl/SOAService.wsdl");

   proxy = new SOAService().getSOAServiceSOAP();*/
   //updating service endpoint 
   Map<String, Object> ctxt = ((BindingProvider)proxy ).getRequestContext();
   ctxt.put(JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE, 8192);
   ctxt.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, WebServiceUrl);

NetBeans 将 WSDL 的副本放在 web-inf/wsdl/client/SOAService 中,所以我不想将它也添加到 META-INF 中。服务类位于 WEB-INF/classes/com/ibm/eci/soaservice/ 中,baseurl 变量包含它的文件系统完整路径(c:\path\to\the\project...\服务)。上面的代码引发了错误:

javax.xml.ws.WebServiceException:无法访问位于:file:/WEB-INF/wsdl/client/SOAService.wsdl 的 WSDL。它失败了: \WEB-INF\wsdl\client\SOAService.wsdl(找不到路径)

那么,首先,我应该更新代理类的 wsdllocation 吗?那我如何告诉 WEB-INF/classes/com/ibm/eci/soaservice 中的 SOAService 类在 \WEB-INF\wsdl\client\SOAService.wsdl 中搜索 WSDL?

已编辑:我找到了另一个链接 - http://jianmingli.com/wp/?cat=41,它表示将 WSDL 放入类路径中。不好意思问:怎么把它放到web应用的classpath中?

【问题讨论】:

类似问题:JAX-WS Loading WSDL from jar 【参考方案1】:

最好的选择是使用 jax-ws-catalog.xml

当您编译本地 WSDL 文件时,覆盖 WSDL 位置并将其设置为类似

http://localhost/wsdl/SOAService.wsdl

别担心,这只是一个 URI 而不是 URL,这意味着您不必在该地址上提供 WSDL。 您可以通过将 wsdllocation 选项传递给 wsdl to java 编译器来做到这一点。

这样做会改变你的代理代码

static 
    URL url = null;
    try 
        URL baseUrl;
        baseUrl = com.ibm.eci.soaservice.SOAService.class.getResource(".");
        url = new URL(baseUrl, "file:/C:/local/path/to/wsdl/SOAService.wsdl");
     catch (MalformedURLException e) 
        logger.warning("Failed to create URL for the wsdl Location: 'file:/C:/local/path/to/wsdl/SOAService.wsdl', retrying as a local file");
        logger.warning(e.getMessage());
    
    SOASERVICE_WSDL_LOCATION = url;

static 
    URL url = null;
    try 
        URL baseUrl;
        baseUrl = com.ibm.eci.soaservice.SOAService.class.getResource(".");
        url = new URL(baseUrl, "http://localhost/wsdl/SOAService.wsdl");
     catch (MalformedURLException e) 
        logger.warning("Failed to create URL for the wsdl Location: 'http://localhost/wsdl/SOAService.wsdl', retrying as a local file");
        logger.warning(e.getMessage());
    
    SOASERVICE_WSDL_LOCATION = url;

注意 file:// 在 URL 构造函数中更改为 http://。

现在出现在 jax-ws-catalog.xml 中。如果没有 jax-ws-catalog.xml,jax-ws 确实会尝试从该位置加载 WSDL

http://localhost/wsdl/SOAService.wsdl 并且失败,因为没有这样的 WSDL 可用。

但是使用 jax-ws-catalog.xml,您可以在 jax-ws 尝试访问 WSDL 时将其重定向到本地打包的 WSDL @

http://localhost/wsdl/SOAService.wsdl 。

这里是 jax-ws-catalog.xml

<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog" prefer="system">
        <system systemId="http://localhost/wsdl/SOAService.wsdl"
                uri="wsdl/SOAService.wsdl"/>
    </catalog>

您正在做的是告诉 jax-ws,它需要从

加载 WSDL http://localhost/wsdl/SOAService.wsdl ,它应该从本地路径 wsdl/SOAService.wsdl 加载它。

现在应该将 wsdl/SOAService.wsdl 和 jax-ws-catalog.xml 放在哪里?那是百万美元的问题,不是吗? 它应该位于应用程序 jar 的 META-INF 目录中。

像这样的

ABCD.jar |__ 元信息 |__ jax-ws-catalog.xml |__ wsdl |__ SOAService.wsdl

这样,您甚至不必覆盖访问代理的客户端中的 URL。 WSDL 是从您的 JAR 中提取的,您可以避免在代码中使用硬编码的文件系统路径。

关于 jax-ws-catalog.xml 的更多信息 http://jax-ws.java.net/nonav/2.1.2m1/docs/catalog-support.html

希望有帮助

【讨论】:

好的,我无法在 Web 应用程序中以这种方式解决问题:我试图将 wsdl 放入 web-inf 中但没有成功,可能是因为我缺乏知识。无论如何,它适用于 jar,所以我将制作一个包装库,因为它应该从一开始就完成。感谢您的支持 我能够成功使用这个答案,我相信这是一个比其他文章和教程文档中的所有其他替代方案更好的解决方案。所以对我来说,这是最佳实践。我只是想知道为什么这个解决方案没有记录在涵盖 JAX-WS 主题的其他官方文章和教程中。【参考方案2】:

我们成功采用的另一种方法是使用 wsimport(来自 Ant,作为 Ant 任务)生成 WS 客户端代理代码并指定 wsdlLocation 属性。

<wsimport debug="true" keep="true" verbose="false" target="2.1" sourcedestdir="$generated.client" wsdl="$src$wsdl.file" wsdlLocation="$wsdl.file">
</wsimport>

由于我们为带有多个 WSDL 的项目运行此脚本,因此脚本会动态解析 $(wsdl.file 值,该值设置为相对于 JavaSource 位置的 /META-INF/wsdl/YourWebServiceName.wsdl(或/src,取决于您如何设置项目)。在构建过程中,将 WSDL 和 XSD 文件复制到此位置并打包到 JAR 文件中。(类似于上面 Bhasakar 描述的解决方案)

MyApp.jar
|__META-INF
   |__wsdl
      |__YourWebServiceName.wsdl
      |__YourWebServiceName_schema1.xsd
      |__YourWebServiceName_schmea2.xsd

注意:确保 WSDL 文件使用对任何导入的 XSD 的相对引用,而不是 http URL:

  <types>
    <xsd:schema>
      <xsd:import namespace="http://valueobject.common.services.xyz.com/" schemaLocation="YourWebService_schema1.xsd"/>
    </xsd:schema>
    <xsd:schema>
      <xsd:import namespace="http://exceptions.util.xyz.com/" schemaLocation="YourWebService_schema2.xsd"/>
    </xsd:schema>
  </types>

生成的代码中,我们发现:

/**
 * This class was generated by the JAX-WS RI.
 * JAX-WS RI 2.2-b05-
 * Generated source version: 2.1
 * 
 */
@WebServiceClient(name = "YourService", targetNamespace = "http://test.webservice.services.xyz.com/", wsdlLocation = "/META-INF/wsdl/YourService.wsdl")
public class YourService_Service
    extends Service


    private final static URL YOURWEBSERVICE_WSDL_LOCATION;
    private final static WebServiceException YOURWEBSERVICE_EXCEPTION;
    private final static QName YOURWEBSERVICE_QNAME = new QName("http://test.webservice.services.xyz.com/", "YourService");

    static 
        YOURWEBSERVICE_WSDL_LOCATION = com.xyz.services.webservice.test.YourService_Service.class.getResource("/META-INF/wsdl/YourService.wsdl");
        WebServiceException e = null;
        if (YOURWEBSERVICE_WSDL_LOCATION == null) 
            e = new WebServiceException("Cannot find '/META-INF/wsdl/YourService.wsdl' wsdl. Place the resource correctly in the classpath.");
        
        YOURWEBSERVICE_EXCEPTION = e;
    

    public YourService_Service() 
        super(__getWsdlLocation(), YOURWEBSERVICE_QNAME);
    

    public YourService_Service(URL wsdlLocation, QName serviceName) 
        super(wsdlLocation, serviceName);
    

    /**
     * 
     * @return
     *     returns YourService
     */
    @WebEndpoint(name = "YourServicePort")
    public YourService getYourServicePort() 
        return super.getPort(new QName("http://test.webservice.services.xyz.com/", "YourServicePort"), YourService.class);
    

    /**
     * 
     * @param features
     *     A list of @link javax.xml.ws.WebServiceFeature to configure on the proxy.  Supported features not in the <code>features</code> parameter will have their default values.
     * @return
     *     returns YourService
     */
    @WebEndpoint(name = "YourServicePort")
    public YourService getYourServicePort(WebServiceFeature... features) 
        return super.getPort(new QName("http://test.webservice.services.xyz.com/", "YourServicePort"), YourService.class, features);
    

    private static URL __getWsdlLocation() 
        if (YOURWEBSERVICE_EXCEPTION!= null) 
            throw YOURWEBSERVICE_EXCEPTION;
        
        return YOURWEBSERVICE_WSDL_LOCATION;
    


也许这也有帮助。这只是一种不使用“目录”方法的不同方法。

【讨论】:

我喜欢这种方法...但为什么是 META-INF 目录? 请注意,这需要使用 JAX-WS RI 2.2,而不是 JDK 6 默认自带的 2.1【参考方案3】:

非常感谢 Bhaskar Karambelkar 的回答,它详细解释并解决了我的问题。但我也想用三个简单的步骤为急于修复的人重新表述答案

    将您的 wsdl 本地位置引用设为 wsdlLocation= "http://localhost/wsdl/yourwsdlname.wsdl" 在 src 下创建一个 META-INF 文件夹。将您的 wsdl 文件/s 放在 META-INF 下的文件夹中,例如 META-INF/wsdl

    在META-INF下创建xml文件jax-ws-catalog.xml如下

    <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog" prefer="system"> <system systemId="http://localhost/wsdl/yourwsdlname.wsdl" uri="wsdl/yourwsdlname.wsdl" /> </catalog>

现在打包你的 jar。不再引用本地目录,全部打包在里面引用

【讨论】:

【参考方案4】:

对于那些仍然来这里寻求解决方案的人,最简单的解决方案是使用&lt;wsdlLocation&gt;,而无需更改任何代码。工作步骤如下:

    将您的 wsdl 放入资源目录,例如:src/main/resource

    在 pom 文件中,添加 wsdlDirectory 和 wsdlLocation(不要错过 / 在 wsdlLocation 的开头),如下所示。而wsdlDirectory用来生成代码,wsdlLocation在运行时用来创建动态代理。

    <wsdlDirectory>src/main/resources/mydir</wsdlDirectory>
    <wsdlLocation>/mydir/my.wsdl</wsdlLocation>
    

    然后在您的 java 代码中(使用无参数构造函数):

    MyPort myPort = new MyPortService().getMyPort();
    

    为了完整起见,我在这里提供完整的代码生成部分,在生成的代码中使用流利的 api。

    <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>2.5</version>
    
    <dependencies>
        <dependency>
            <groupId>org.jvnet.jaxb2_commons</groupId>
            <artifactId>jaxb2-fluent-api</artifactId>
            <version>3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.ws</groupId>
            <artifactId>jaxws-tools</artifactId>
            <version>2.3.0</version>
        </dependency>
    </dependencies>
    
    <executions>
        <execution>
            <id>wsdl-to-java-generator</id>
            <goals>
                <goal>wsimport</goal>
            </goals>
            <configuration>
                <xjcArgs>
                    <xjcArg>-Xfluent-api</xjcArg>
                </xjcArgs>
                <keep>true</keep>
                <wsdlDirectory>src/main/resources/package</wsdlDirectory>
                <wsdlLocation>/package/my.wsdl</wsdlLocation>
                <sourceDestDir>$project.build.directory/generated-sources/annotations/jaxb</sourceDestDir>
                <packageName>full.package.here</packageName>
            </configuration>
        </execution>
    </executions>
    

【讨论】:

【参考方案5】:

对于那些使用 Spring 的人,您可以使用类路径协议简单地引用任何类路径资源。所以在 wsdlLocation 的情况下,这变成:

<wsdlLocation>classpath:META-INF/webservice.wsdl</wsdlLocation>

请注意,这不是标准的 Java 行为。另见:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/resources.html

【讨论】:

【参考方案6】:

遇到了确切与此处描述的相同的问题。不管我做了什么,按照上面的例子,改变我的 WSDL 文件的位置(在我们的例子中来自一个 web 服务器),它仍然引用嵌入在服务器进程的源树中的原始位置。

在尝试调试此问题 MANY 小时后,我注意到异常总是从完全相同的行抛出(在我的情况下为 41)。最后今天早上,我决定将我的源客户端代码发送给我们的贸易伙伴,这样他们至少可以了解代码的外观,但也许可以构建自己的代码。对于我的震惊horror,我在客户端源代码树中发现一堆类文件与我的 .java 文件混在一起。多么奇怪!我怀疑这些是 JAX-WS 客户端构建器工具的副产品。

一旦我删除了那些愚蠢的 .class 文件并对客户端代码进行了彻底的清理和重建,一切都运行良好!!太可怕了!!

YMMV, 安德鲁

【讨论】:

【参考方案7】:

对于那些使用多个 wsdl 文件的人:

pom.xml

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.6</version>
<executions>
    <execution>
        <phase>generate-sources</phase>
        <goals>
            <goal>wsimport</goal>
        </goals>
        <configuration>
            <bindingDirectory>$basedir/src/main/resources/jaxws</bindingDirectory>
            <bindingFiles>
                <bindingFile>binding.xjb</bindingFile>
            </bindingFiles>
            <wsdlDirectory>$basedir/src/main/resources/wsdl</wsdlDirectory>
            <wsdlFiles>
                <wsdlFile>VN_PCSApplicationManagementService_v21.xml</wsdlFile>
                <wsdlFile>VN_PCSApplicationOfferManagementService_v7.xml</wsdlFile>
                <wsdlFile>VN_PCSOnlineDebtService_v2.xml</wsdlFile>
            </wsdlFiles>
        </configuration>
    </execution>
</executions>

创建服务客户端时:

@Bean
public ApplicationOfferManagementWSV7 getAppOfferWS() 
    String wsdlLocation = OnlineDebtWSv2Soap11QSService.class.getAnnotation(WebServiceClient.class).wsdlLocation().replaceFirst(".+wsdl/", "/wsdl/");
    URL url = this.getClass().getResource(wsdlLocation);
    ApplicationOfferManagementWSV7 applicationManagementWSV21 = new ApplicationOfferManagementWSV7Soap11QSService(url)
            .getApplicationOfferManagementWSV7Soap11QSPort();

    BindingProvider binding = (BindingProvider) applicationManagementWSV21;
    binding.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, username);
    binding.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, password);
    binding.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, offerEndpoint);

    return applicationManagementWSV21;

【讨论】:

以上是关于JAX-WS 客户端:访问本地 WSDL 的正确路径是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免需要在 CXF 或 JAX-WS 生成的 Web 服务客户端中指定 WSDL 位置?

如何避免在CXF或JAX-WS生成的Web服务客户端中指定WSDL位置?

带有 Axis 服务的 JAX-WS 客户端

JDK Tools - wsimport: 编译 WSDL 生成 JAX-WS 规范的 Java 类

如何在 JAX-WS 客户端中指定 ReplyTo EndpointReference?

为 JAX-RPC Web 服务生成 JAX-WS 客户端存根?