วิธีหลีกเลี่ยงความจำเป็นในการระบุตำแหน่ง WSDL ในไคลเอนต์เว็บเซอร์วิซที่สร้างโดย CXF หรือ JAX-WS?


165

เมื่อฉันสร้างลูกค้าเว็บเซอร์โดยใช้ wsdl2java จาก CXF (ซึ่งสร้างบางอย่างที่คล้ายกับ wsimport) ผ่าน maven บริการของฉันเริ่มต้นด้วยรหัสเช่นนี้:

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

เส้นทางสัมบูรณ์ hardcoded แย่จริงๆ คลาสที่สร้างขึ้นจะไม่ทำงานในคอมพิวเตอร์เครื่องอื่น ๆ

แนวคิดแรกคือการวางไฟล์ WSDL (รวมถึงทุกอย่างที่นำเข้า WSDL และ XSD อื่น ๆ ) ที่ใดที่หนึ่งในไฟล์ jar และ classpath แต่เราต้องการหลีกเลี่ยงสิ่งนี้ เนื่องจากทุกสิ่งนั้นถูกสร้างขึ้นโดย CXF และ JAXB ซึ่งอยู่ใน WSDLs และ XSD เราจึงไม่เห็นจุดที่ต้องรู้ WSDL ในขณะทำงาน

แอ็ตทริบิวต์ wsdlLocation มีวัตถุประสงค์เพื่อแทนที่ตำแหน่ง WSDL (อย่างน้อยนี่คือสิ่งที่ฉันได้อ่านที่ไหนสักแห่ง) และค่าเริ่มต้นคือ "" เนื่องจากเราใช้ maven เราจึงพยายามรวม<wsdlLocation></wsdlLocation>ไว้ในการกำหนดค่าของ CXF เพื่อพยายามบังคับให้เครื่องกำเนิดแหล่งที่มาปล่อยให้ 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. เราต้องการตำแหน่ง WSDL จริง ๆ หรือไม่แม้ว่าจะสร้างคลาสทั้งหมดโดย CXF และ JAXB ถ้าใช่ทำไม

  2. หากเราไม่ต้องการตำแหน่ง WSDL วิธีที่เหมาะสมและสะอาดในการทำให้ CXF ไม่สร้างและหลีกเลี่ยงมันคืออะไร?

  3. มีผลข้างเคียงอะไรบ้างที่เราจะได้รับจากการแฮ็คนั้น เรายังไม่สามารถทดสอบสิ่งนั้นเพื่อดูว่าเกิดอะไรขึ้นดังนั้นหากใครบางคนสามารถบอกล่วงหน้าได้มันจะดี

คำตอบ:


206

ในที่สุดฉันก็พบคำตอบที่ถูกต้องสำหรับคำถามนี้ในวันนี้

<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 จะอยู่บน classpath แทนที่จะเป็นพา ธ สัมบูรณ์ จากนั้นมันจะสร้างโค้ดที่คล้ายกับสิ่งนี้:

@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 เท่านั้น


8
เมื่อใช้ปลั๊กอิน JAX Maven แทน CXF ให้ละเว้นclasspath:ใน<wsdlLocation...บรรทัด
Twilite

มีใครประสบปัญหาชื่อกับรหัสที่สร้างโดยวิธีการดังกล่าวหรือไม่
Narendra Jaggi

คุณมีรายการ wsdl แต่ละรายการถ้าคุณมีหลาย ๆ เป็นไปได้ไหมที่จะหลีกเลี่ยงสิ่งนั้น?
pitseeker

21

เราใช้

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

กล่าวอีกนัยหนึ่งให้ใช้เส้นทางที่สัมพันธ์กับ classpath

ฉันเชื่อว่าอาจจำเป็นต้องใช้ WSDL ในขณะรันไทม์เพื่อตรวจสอบข้อความระหว่าง marshal / unmarshal


17

สำหรับผู้ที่ใช้org.jvnet.jax-ws-commons:jaxws-maven-pluginเพื่อสร้างไคลเอนต์จาก WSDL ณ เวลาบิลด์:

  • วาง WSDL ไว้ในที่ของคุณ src/main/resources
  • อย่าได้คำนำหน้าwsdlLocationด้วยclasspath:
  • ทำคำนำหน้าwsdlLocationด้วย/

ตัวอย่าง:

  • WSDL ถูกเก็บไว้ใน /src/main/resources/foo/bar.wsdl
  • กำหนดค่าjaxws-maven-pluginด้วย<wsdlDirectory>${basedir}/src/main/resources/foo</wsdlDirectory>และ<wsdlLocation>/foo/bar.wsdl</wsdlLocation>

ทำไมไม่ใช้คำนำหน้า "wsdlLocation กับ classpath" ฉันใช้มันและใช้งานได้
Mohammad Sadegh Rafiei

9

1) ในบางกรณีใช่ หาก WSDL มีสิ่งต่าง ๆ เช่นนโยบายและที่ควบคุมพฤติกรรมของรันไทม์อาจจำเป็นต้องใช้ WSDL ตอนรันไทม์ สิ่งประดิษฐ์ไม่ได้ถูกสร้างขึ้นเพื่อสิ่งที่เกี่ยวข้องกับนโยบายและเช่นนั้น นอกจากนี้ในบางกรณี RPC / ตัวอักษรที่คลุมเครือไม่ใช่เนมสเปซทั้งหมดที่จำเป็นต้องใช้จะมีเอาต์พุตในโค้ดที่สร้างขึ้น (ต่อข้อมูลจำเพาะ) ดังนั้น wsdl จะต้องการสำหรับพวกเขา กรณีที่คลุมเครือ

2) ฉันคิดว่าสิ่งที่ต้องการจะทำงาน CXF รุ่นใด นั่นฟังดูเหมือนแมลง คุณสามารถลองสตริงว่างในนั้น (เพียงช่องว่าง) ไม่แน่ใจว่าใช้งานได้หรือไม่ ที่กล่าวว่าในรหัสของคุณคุณสามารถใช้ตัวสร้างที่ใช้ WSDL URL และเพียงผ่าน null wsdl จะไม่ถูกใช้

3) เพียงข้อ จำกัด ข้างต้น


มันเป็น CXF 2.3.1 ใหม่ล่าสุด เปิดตัวเพียง 8 วันที่ผ่านมา ผ่านโมฆะเป็นความคิดที่ดีฉันควรเห็นคำตอบที่ชัดเจนก่อนนี้ ฉันจะลองใช้ช่องว่าง
Victor Stafusa

ไม่ช่องว่างไม่เหมือนกับอะไรเลย ie: แท็ก XML ถูกละเว้นทั้งหมด
Victor Stafusa

5

ฉันสามารถสร้าง

static {
    WSDL_LOCATION = null;
}

โดยการกำหนดค่าไฟล์ pom ให้มีค่า null สำหรับ 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>

2
โซลูชันนี้ใช้ไม่ได้กับฉันด้วย CXF 3.1.0 มีข้อผิดพลาด org.apache.cxf.tools.common.toolspec.parser.BadUsageException: ตัวเลือกที่ไม่คาดคิด: -wsdlLocation
Chandru

4

เป็นไปได้หรือไม่ที่คุณสามารถหลีกเลี่ยงการใช้ wsdl2java คุณสามารถใช้ CXF FrontEnd API ได้ทันทีเพื่อเรียกใช้ SOAP Webservice ของคุณ สิ่งที่จับได้เพียงอย่างเดียวคือคุณต้องสร้าง 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/


2
ไฟล์ WSDL นั้นซับซ้อนอย่างมากดังนั้นเราจึงใช้การสร้างอัตโนมัติเป็นวิธีการตรวจสอบความเข้ากันได้ การสร้างอัตโนมัติสร้าง VO และ SEIs ที่ซับซ้อนพอ ๆ กัน เราเลือกที่จะใช้ชุดของวัตถุโดเมนแยกต่างหากที่แยกจากกันกับวัตถุที่สร้างอัตโนมัติดังนั้นเราจึงไม่ได้แทรกแซงการสร้างอัตโนมัติหรือถูก จำกัด หรือขับเคลื่อนโดยวัตถุนั้น VO ที่สร้างอัตโนมัติถูกใช้ในบริบทของการสื่อสารบริการเท่านั้นและเราทำให้พวกเขามีอายุสั้นที่สุดเท่าที่จะทำได้ กล่าวอีกอย่างหนึ่งคือความกังวลของเราคือหลีกเลี่ยงความจำเป็นในการใช้รหัสและจัดการ VO ทั้งหมดด้วยตนเอง
Victor Stafusa

2
ฉันเห็นด้วยกับวิกเตอร์เนื่องจากการบำรุงรักษา VO ด้วยตนเองอาจเป็นการเสียเวลาและความเสี่ยงของความแตกต่างทำให้มองเห็นได้และมีคุณสมบัติมากขึ้นหรือน้อยลง .. นั่นคือจุดประสงค์ของ wsdl2java นั่นคือเหตุผลที่มีประโยชน์และปลอดภัย!
Donatello

4

อัพเดทสำหรับ CXF 3.1.7

ในกรณีของฉันฉันใส่ไฟล์ WSDL ในsrc/main/resourcesและเพิ่มเส้นทางนี้ไปยัง Srouces ของฉันใน Eclipse (คลิกขวาที่ 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 และไม่ได้มาจาก Absolute File-Path

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

<configuration> <sourceRoot>${basedir}/src/main/java/</sourceRoot> <wsdlRoot>${basedir}/src/main/resources/</wsdlRoot> <includes> <include>*.wsdl</include> </includes> </configuration> ฉันรวมไฟล์. wsdl ทั้งหมดในคลาสพา ธ แล้วฉันจะระบุตำแหน่ง wsdl ได้อย่างไรเพื่อให้ไฟล์. java ทุกไฟล์ที่สร้างขึ้นสามารถเชื่อมต่อตามเส้นทาง. wsdl ได้ ขอบคุณล่วงหน้า. @Mazy
Khalid Shah

2

อย่างจริงจังคำตอบยอดนิยมไม่ทำงานสำหรับฉัน พยายาม cxf.version 2.4.1 และ 3.0.10 และสร้างเส้นทางที่แน่นอนด้วย wsdlLocation ทุกครั้ง

ทางออกของฉันคือการใช้wsdl2javaคำสั่งในด้วยapache-cxf-3.0.10\bin\ -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

0

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.