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

当我通过 maven 使用 CXF 中的 wsdl2java 生成一个 webservice 客户端时(它会生成类似于 wsimport 的代码) ,我的服务以下面这样的代码开始:

@WebServiceClient(name = "StatusManagement",
wsdlLocation = "c:/some_absolute_path_to_a_wsdl_file.wsdl",
targetNamespace = "http://tempuri.org/")
public class StatusManagement extends Service {


public final static URL WSDL_LOCATION;
public final static QName SERVICE = new QName("http://tempuri.org/", "StatusManagement");
public final static QName WSHttpBindingIStatus = new QName("http://tempuri.org/", "WSHttpBinding_IStatus");
static {
URL url = null;
try {
url = new URL("c:/some_absolute_path_to_a_wsdl_file.wsdl");
} catch (MalformedURLException e) {
System.err.println("Can not initialize the default wsdl from c:/some_absolute_path_to_a_wsdl_file.wsdl");
// e.printStackTrace();
}
WSDL_LOCATION = url;
}

硬编码的绝对路径真是糟透了。生成的类只能在我的计算机上使用。

第一个想法是将 WSDL 文件(加上它导入的所有内容、其他 WSDL 和 XSD)放在 jar 文件和类路径中的某个地方。但我们不想这样。由于所有这些都是由基于 WSDL 和 XSD 的 CXF 和 JAXB 生成的,因此我们认为在运行时没有必要了解 WSDL。

WsdlLocation 属性旨在覆盖 WSDL 位置(至少这是我在某处读到的) ,它的默认值是“”。由于我们使用的是 maven,因此我们尝试在 CXF 的配置中包含 <wsdlLocation></wsdlLocation>,以强制源生成器将 wsdlLocation 保留为空。但是,这只会使它忽略 XML 标记,因为它是空的。我们用 <wsdlLocation>" + "</wsdlLocation>做了一个非常丑陋可耻的黑客行为。

这也改变了其他地方:

@WebServiceClient(name = "StatusManagement",
wsdlLocation = "" + "",
targetNamespace = "http://tempuri.org/")
public class StatusManagement extends Service {


public final static URL WSDL_LOCATION;
public final static QName SERVICE = new QName("http://tempuri.org/", "StatusManagement");
public final static QName WSHttpBindingIStatus = new QName("http://tempuri.org/", "WSHttpBinding_IStatus");
static {
URL url = null;
try {
url = new URL("" + "");
} catch (MalformedURLException e) {
System.err.println("Can not initialize the default wsdl from " + "");
// e.printStackTrace();
}
WSDL_LOCATION = url;
}

所以,我的问题是:

  1. 即使所有的类都是由 CXF 和 JAXB 生成的,我们真的需要 WSDL 位置吗?如果是,为什么?

  2. 如果我们并不真正需要 WSDL 位置,那么什么才是使 CXF 不生成并完全避免它的正确和干净的方法呢?

  3. 黑进去会有什么副作用?我们仍然不能测试,看看会发生什么,所以如果有人能提前说,这将是很好的。

186412 次浏览

1)在某些情况下,是的。如果 WSDL 包含诸如策略之类的东西,并指导运行时行为,那么在运行时可能需要 WSDL。不会为策略相关的事情生成工件。另外,在一些模糊的 RPC/Literal 情况下,并不是所有需要的名称空间都会在生成的代码中输出(每个规范)。因此,他们需要 wsdl。不过是些不起眼的案子。

2)我以为这样会有用。什么版本的 CXF?听起来像是窃听器。您可以在这里尝试一个空字符串(只有空格)。不知道这样行不行。也就是说,在您的代码中,您可以使用接受 WSDL URL 并只传递 null 的构造函数。Wsdl 不会被使用。

3)以上的限制。

我们用

wsdlLocation = "WEB-INF/wsdl/WSDL.wsdl"

换句话说,使用相对于类路径的路径。

我相信在编组/解组期间,在运行时验证消息可能需要 WSDL。

我能够产生

static {
WSDL_LOCATION = null;
}

通过将 pom 文件配置为 wsdurl 的 null:

    <plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${basedir}/target/generated/src/main/java</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${basedir}/src/main/resources/service.wsdl</wsdl>
<extraargs>
<extraarg>-client</extraarg>
<extraarg>-wsdlLocation</extraarg>
<wsdlurl />
</extraargs>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>

我今天终于找到了这个问题的正确答案。

<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated-sources/cxf</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/FooService.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/FooService.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>

请注意,我已经在 wsdlLocation中的值前面加上了 classpath:。这告诉插件 wsdl 将位于类路径上,而不是绝对路径上。然后它将生成类似于下面这样的代码:

@WebServiceClient(name = "FooService",
wsdlLocation = "classpath:wsdl/FooService.wsdl",
targetNamespace = "http://org/example/foo")
public class Foo_Service extends Service {


public final static URL WSDL_LOCATION;


public final static QName SERVICE = new QName("http://org/example/foo", "Foo");
public final static QName FooSOAPOverHTTP = new QName("http://org/example/foo", "Foo_SOAPOverHTTP");
static {
URL url = Foo_Service.class.getClassLoader().getResource("wsdl/FooService.wsdl");
if (url == null) {
java.util.logging.Logger.getLogger(Foo_Service.class.getName())
.log(java.util.logging.Level.INFO,
"Can not initialize the default wsdl from {0}", "classpath:wsdl/FooService.wsdl");
}
WSDL_LOCATION = url;
}

注意,这只适用于 cxf-codegen-plugin 版本2.4.1或更新的版本。

有没有可能避免使用 wsdl2java?您可以直接使用 CXFFrontEndAPI 来调用您的 SOAPWebservice。唯一的问题是您需要在客户端创建 SEI 和 VOs。下面是一个示例代码。

package com.aranin.weblog4j.client;


import com.aranin.weblog4j.services.BookShelfService;
import com.aranin.weblog4j.vo.BookVO;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;


public class DemoClient {
public static void main(String[] args){
String serviceUrl = "http://localhost:8080/weblog4jdemo/bookshelfservice";
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(BookShelfService.class);
factory.setAddress(serviceUrl);
BookShelfService bookService = (BookShelfService) factory.create();


//insert book
BookVO bookVO = new BookVO();
bookVO.setAuthor("Issac Asimov");
bookVO.setBookName("Foundation and Earth");


String result = bookService.insertBook(bookVO);


System.out.println("result : " + result);


bookVO = new BookVO();
bookVO.setAuthor("Issac Asimov");
bookVO.setBookName("Foundation and Empire");


result = bookService.insertBook(bookVO);


System.out.println("result : " + result);


bookVO = new BookVO();
bookVO.setAuthor("Arthur C Clarke");
bookVO.setBookName("Rama Revealed");


result = bookService.insertBook(bookVO);


System.out.println("result : " + result);


//retrieve book


bookVO = bookService.getBook("Foundation and Earth");


System.out.println("book name : " + bookVO.getBookName());
System.out.println("book author : " + bookVO.getAuthor());


}
}

你可以在这里看到完整的教程 http://weblog4j.com/2012/05/01/developing-soap-web-service-using-apache-cxf/

对于那些使用 org.jvnet.jax-ws-commons:jaxws-maven-plugin在构建时从 WSDL 生成客户端的人:

  • 将 WSDL 放在 src/main/resources中的某个位置
  • 使用 classpath:作为 wsdlLocation的前缀
  • 请在 wsdlLocation前面加上 /

例如:

  • WSDL 存储在 /src/main/resources/foo/bar.wsdl
  • 使用 <wsdlDirectory>${basedir}/src/main/resources/foo</wsdlDirectory><wsdlLocation>/foo/bar.wsdl</wsdlLocation>配置 jaxws-maven-plugin

说真的,最佳答案对我不起作用。 尝试 cxf.version 2.4.1和3.0.10. 并每次使用 wsdlLocation 生成绝对路径。

我的解决方案是在 apache-cxf-3.0.10\bin\中使用 wsdl2java命令 与 -wsdlLocation classpath:wsdl/QueryService.wsdl

详情:

    wsdl2java -encoding utf-8 -p com.jeiao.boss.testQueryService -impl -wsdlLocation classpath:wsdl/testQueryService.wsdl http://127.0.0.1:9999/platf/testQueryService?wsdl

CXF 3.1.7的更新

在我的示例中,我将 WSDL 文件放在 src/main/resources中,并将这个路径添加到 Eclipse 中的 Sources 中(右键单击 Project-> Build Path-> Configure Build Path...-> Source [ Tab ]-> Add Folder)。

这里是如何我的 pom文件看起来像,可以看到有没有 wsdlLocation 所需选择:

       <plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>classpath:wsdl/FOO_SERVICE.wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>

这里是生成的服务。可以看到,URL 是从 ClassLoader 获取的,而不是从绝对文件路径获取的

@WebServiceClient(name = "EventService",
wsdlLocation = "classpath:wsdl/FOO_SERVICE.wsdl",
targetNamespace = "http://www.sas.com/xml/schema/sas-svcs/rtdm-1.1/wsdl/")
public class EventService extends Service {


public final static URL WSDL_LOCATION;


public final static QName SERVICE = new QName("http://www.sas.com/xml/schema/sas-svcs/rtdm-1.1/wsdl/", "EventService");
public final static QName EventPort = new QName("http://www.sas.com/xml/schema/sas-svcs/rtdm-1.1/wsdl/", "EventPort");
static {
URL url = EventService.class.getClassLoader().getResource("wsdl/FOO_SERVICE.wsdl");
if (url == null) {
java.util.logging.Logger.getLogger(EventService.class.getName())
.log(java.util.logging.Level.INFO,
"Can not initialize the default wsdl from {0}", "classpath:wsdl/FOO_SERVICE.wsdl");
}
WSDL_LOCATION = url;
}

@ Martin Devillers 解决方案运行良好。为了完整起见,提供以下步骤:

  1. 将 wsdl 放入资源目录,如 src/main/resource
  2. 在 pom 文件中,添加 wsdlDirectory 和 wsdlLocation (不要错过/在 wsdlLocation 的开头) ,如下所示。WsdlDirectory 用于生成代码,wsdlLocation 用于在运行时创建动态代理。

    <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. Here is the full code generation part in pom file, 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>
    

我可以自动生成

    static {
WSDL_LOCATION = null;
}

    <wsdlLocation>null</wsdlLocation>

不过,这样做的目的对我来说似乎有限。.我试图尽可能快地实现短期 WS 客户端,最小化 svc = new MyService(); binding = svc.getMySoapPort();上的初始化,但现在我明白了

INFO: Creating Service {http://www.example.com/MyApi}MyApiService from class com.company.MyApiPortType

而不是

INFO: Creating Service {http://www.example.com/MyApi}MyApiService from WSDL ...


看起来,new MyService();无论如何都是昂贵的,所以我必须考虑重用它的长寿命并发实例... 所以 WSDL 的运行时解析将不再是一个问题。