【问题标题】:How to avoid the need to specify the WSDL location in a CXF or JAX-WS generated webservice client?如何避免需要在 CXF 或 JAX-WS 生成的 Web 服务客户端中指定 WSDL 位置?
【发布时间】:2011-05-26 04:58:22
【问题描述】:

当我通过 maven 使用 CXF 中的 wsdl2java(生成类似于 wsimport 的内容)生成 Web 服务客户端时,我的服务以如下代码开始:

@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. 使用该 hack 会产生什么不良副作用?我们仍然无法对其进行测试以了解会发生什么,所以如果有人可以提前说,那就太好了。

【问题讨论】:

    标签: java wsdl cxf wsdl2java


    【解决方案1】:

    我能够自动生成

        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 的运行时解析将不是问题。

    【讨论】:

      【解决方案2】:

      @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. 然后在您的 java 代码中(使用无参数构造函数):

        MyPort myPort = new MyPortService().getMyPort();
        
      4. 这里是pom文件中完整的代码生成部分,生成的代码中有流利的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>
        

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

        • 当使用 JAX Maven 插件而不是 CXF 时,在 &lt;wsdlLocation... 行中省略 classpath:
        • 是否有人面临上述方法生成的代码的命名空间问题?
        • 如果您有多个 wsdl,您是否必须单独列出每个 wsdl?有没有可能避免这种情况?
        【解决方案4】:

        说真的,最佳答案对我不起作用。 尝试了 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
        

        【讨论】:

          【解决方案5】:

          CXF 3.1.7 更新

          在我的情况下,我将 WSDL 文件放在 src/main/resources 中,并将此路径添加到我在 Eclipse 中的 Srouces 中(右键单击项目-> 构建路径 -> 配置构建路径...-> 源 [Tab] -> 添加文件夹)。

          这是我的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;   
          }
          

          【讨论】:

          • &lt;configuration&gt; &lt;sourceRoot&gt;${basedir}/src/main/java/&lt;/sourceRoot&gt; &lt;wsdlRoot&gt;${basedir}/src/main/resources/&lt;/wsdlRoot&gt; &lt;includes&gt; &lt;include&gt;*.wsdl&lt;/include&gt; &lt;/includes&gt; &lt;/configuration&gt; 我在类路径中包含所有 .wsdl 文件,那么如何指定 wsdl 位置,以便每个生成的 .java 文件包含各自的 .wsdl 路径?提前致谢。 @Mazy
          【解决方案6】:

          我能够生成

          static {
              WSDL_LOCATION = null;
          }
          

          通过将 pom 文件配置为 wsdlurl 为空:

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

          【讨论】:

          • 此解决方案不适用于 CXF 3.1.0。得到一个错误 org.apache.cxf.tools.common.toolspec.parser.BadUsageException: Unexpected option: -wsdlLocation
          【解决方案7】:

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

          • 将 WSDL 放在 src/main/resources 中的某个位置
          • 不要wsdlLocation前加上classpath:
          • wsdlLocation 前加上/

          例子:

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

          【讨论】:

          【解决方案8】:

          是否可以避免使用 wsdl2java?您可以立即使用 CXF 前端 API 来调用您的 SOAP Web 服务。唯一的问题是您需要在客户端创建 SEI 和 VO。这是一个示例代码。

          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/

          【讨论】:

          • WSDL 文件非常复杂,因此我们使用自动生成来确保兼容性。自动生成创建了一些同样非常复杂的 VO 和 SEI。我们选择使用与自动生成的对象完全解耦的单独的域对象集,因此我们不会干扰自动生成,也不会受到它的限制或驱动。自动生成的 VO 仅在服务通信的上下文中使用,我们尽可能地保持它们的短暂性。换句话说,我们关注的一个问题是避免手动编码和管理所有 VO。
          • 我同意 Victor 的观点,因为手动维护 VO 可能会浪费时间,并且存在差异的风险,或多或少可见和合格.. 这正是 wsdl2java 的目的,这就是它有用和安全!
          【解决方案9】:

          我们使用

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

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

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

          【讨论】:

            【解决方案10】:

            1) 在某些情况下,是的。如果 WSDL 包含诸如策略等指导运行时行为的内容,则在运行时可能需要 WSDL。不会为与政策相关的事物等生成工件。此外,在一些晦涩的 RPC/Literal 情况下,并非所有需要的命名空间都在生成的代码中输出(根据规范)。因此,他们需要 wsdl。虽然是晦涩的案例。

            2)我认为类似的东西会起作用。什么版本的CXF?这听起来像一个错误。您可以在其中尝试一个空字符串(只有空格)。不确定这是否有效。也就是说,在您的代码中,您可以使用接受 WSDL URL 的构造函数并只传递 null。不会使用 wsdl。

            3) 只是上述限制。

            【讨论】:

            • 最新的CXF 2.3.1。仅在 8 天前发布。传递 null 是个好主意,我之前应该看到这个明显的答案。我仍然会尝试这些空间。
            • 不,空格和什么都没有一样。即:XML 标记被完全忽略。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-03-25
            • 2011-03-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-01-10
            相关资源
            最近更新 更多