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

93
问题是我需要从所提供的文件中构建Web服务客户端。我已经将此文件存储在本地文件系统上,只要保持WSDL文件在正确的文件系统文件夹中,一切都很好。当我将其部署到服务器或从文件系统文件夹中删除WSDL时,代理无法找到WSDL并引发错误。我在网上搜索并找到了以下帖子,但我无法使其工作:
JAX-WS从jar加载WSDL
http://www.java.net/forum/topic/glassfish/metro-and-jaxb/client-jar-cant-find-local-wsdl-0
http://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...\soaservice)。上述代码引发了错误:

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应用程序类路径中?


类似问题:JAX-WS从jar加载WSDL - sleske
8个回答

120
最佳选择是使用jax-ws-catalog.xml。
当您编译本地WSDL文件时,请覆盖WSDL位置并将其设置为类似以下内容:
http://localhost/wsdl/SOAService.wsdl
不用担心,这只是一个URI而不是URL,意味着您不必在那个地址上提供WSDL。
您可以通过向wsdl到java编译器传递wsdllocation选项来实现此操作。
这样做将更改您的代理代码,从而避免了以下问题:
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;
}

注意URL构造器中的file://已更改为http://。

现在进入jax-ws-catalog.xml。如果没有jax-ws-catalog.xml,jax-ws确实会尝试从位置

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

但是通过jax-ws-catalog.xml,您可以在jax-ws尝试访问@

http://localhost/wsdl/SOAService.wsdl
的WSDL时将其重定向到本地打包的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,每当需要从
http://localhost/wsdl/SOAService.wsdl
加载WSDL时,它应该从本地路径wsdl/SOAService.wsdl进行加载。
现在,您应该将wsdl/SOAService.wsdl和jax-ws-catalog.xml放在哪里?这不是百万富翁问题吗?
应该放在应用程序jar的META-INF目录中。
所以像这样:
ABCD.jar  
|__ META-INF    
    |__ 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文件运行,所以我会创建一个包装库,就像一开始应该做的那样。谢谢你的支持。 - user260192
我能成功地使用这个答案,我相信这是比其他文章和教程记录的所有替代方案都更好的解决方案。对我来说,这就是最佳实践。我只是想知道为什么这个解决方案没有被记录在涵盖JAX-WS主题的其他官方文章和教程中。 - Rahul Khimasia

19

我们采用的另一种成功方法是使用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位置(或/src,具体取决于您如何设置项目)的/META-INF/wsdl/YourWebServiceName.wsdl。在构建过程中,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目录? - IcedDante
1
请注意,这需要使用JAX-WS RI 2.2,而不是默认附带于JDK 6的2.1版本。 - ᄂ ᄀ

5

对于那些仍在寻找解决方法的人,最简单的解决方法就是使用<wsdlLocation>,而无需更改任何代码。下面是工作步骤:

  1. Put your wsdl to resource directory like : src/main/resource
  2. In pom file, add both wsdlDirectory and wsdlLocation(don't miss / at the beginning of wsdlLocation), like below. While wsdlDirectory is used to generate code and wsdlLocation is used at runtime to create dynamic proxy.

    <wsdlDirectory>src/main/resources/mydir</wsdlDirectory>
    <wsdlLocation>/mydir/my.wsdl</wsdlLocation>
    
  3. Then in your java code(with no-arg constructor):

    MyPort myPort = new MyPortService().getMyPort();
    
  4. For completeness, I am providing here full code generation part, with fluent api in generated code.

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


4
感谢Bhaskar Karambelkar的答案,详细解释并解决了我的问题。但是我想用三个简单的步骤重新表述答案,以便于那些急于解决问题的人:
  1. 将你的wsdl本地位置引用设置为 wsdlLocation= "http://localhost/wsdl/yourwsdlname.wsdl"
  2. 在src目录下创建一个名为META-INF的文件夹,将你的wsdl文件放在其子文件夹中,比如META-INF/wsdl
  3. 在META-INF文件夹下创建一个名为jax-ws-catalog.xml的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文件,不需要再引用本地目录,一切都被打包并引用了。

3

0

我遇到了与此处描述的完全相同的问题。无论我如何更改我的WSDL文件的位置(在我们的情况下是从Web服务器),都按照上面的示例,它仍然引用嵌入在服务器进程源树中的原始位置。

经过多次尝试调试,我注意到异常总是从完全相同的行(在我的情况下是第41行)抛出。终于今天早上,我决定将我的源客户端代码发送给我们的交易伙伴,以便他们至少可以理解代码的外观,但也许可以构建自己的代码。令我感到震惊恐惧的是,在我的客户端源树中混杂着一堆.class文件和.java文件。这太奇怪了!我怀疑这些是JAX-WS客户端构建器工具的副产品。

一旦我删除了那些愚蠢的.class文件并对客户端代码进行了完整的清理和重建,一切都运行得非常完美!真是太离谱了!

你的情况可能会有所不同, 安德鲁


0
在我的情况下,我必须从.classpath文件中删除(excluding="META-INF"),以便为"src"条目进行设置。然后在代理类中将wsdl路径从/META-INF更改为META-INF。

0

对于那些使用多个 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;
}

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