【Java】MetroでWSDLからSOAPクライアントのJavaソースコードを自動生成する
前回、WebLogic上で簡単にSOAPのWebサービスを公開する方法を検証した。
今回は公開されたWSDLだけから、そのSOAPサービスを利用するためのJavaクライアントコードを実装してみる。実装と言っても、SOAPクライアントの部分はWSDLから自動生成することができるので、難しいWSDLの定義や通信を意識する必要は全くない。WSDLからの自動生成にはMetroというツールを利用した。以下、詳しく見ていく。
MetroでSOAPクライアントを自動生成する
GlassFishの中で、Metroというオープンソースのツールがあり、これでWSDLからJavaコードを自動生成することができる。GlashFishサーバにも含まれているらしいが、今回はスタンドアロン版を入手した(記事執筆時点での最新版である2.3.1をダウンロード)。
- Metro
展開すると、binディレクトリにWSDLをインポートしJavaクラスを作成するwsimport.shがある。
$ unzip metro-standalone-2.3.1.zip $ cd metro/bin/ $ ls wsgen.bat wsgen.sh wsimport.bat wsimport.sh
使うためには、METRO_HOME環境変数にzip展開先のパスを指定しておく。
$ export METRO_HOME=/home/user/metro
ソース出力先ディレクトリを作成し、wsimportを実行する。
- keepオプションはソースコードを生成するオプションで、これを付与しないと.classファイルのみ作成される。また、-sオプションで、ソースコード出力先を指定している。
$ mkdir src $ sh wsimport.sh -keep -s src http://vmora7:7001/SoapApp/CalculatorService?wsdl parsing WSDL... GConf Error: Client failed to connect to the D-BUS daemon: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11 GConf Error: Client failed to connect to the D-BUS daemon: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11 GConf Error: Client failed to connect to the D-BUS daemon: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11 GConf Error: Client failed to connect to the D-BUS daemon: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11 GConf Error: Client failed to connect to the D-BUS daemon: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11 GConf Error: Client failed to connect to the D-BUS daemon: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11 GConf Error: Client failed to connect to the D-BUS daemon: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11 GConf Error: Client failed to connect to the D-BUS daemon: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11 Generating code... Compiling code...
何かエラーが出ているようだが、コード生成自体は問題なさそう。
$ find src src src/web src/web/Add.java src/web/AddResponse.java src/web/Calc.java src/web/Calculator.java src/web/CalculatorService.java src/web/ObjectFactory.java src/web/Sub.java src/web/SubResponse.java src/web/package-info.java
とりあえずEclipseにて新規Javaプロジェクトを作成し、生成されたソースコードを配置する。ビルドも正常に完了するよう。
さて、作られたソース・ファイルの中身を見ていく。前回Webサービスを公開したときに作ったコードと似たような名前のクラスができているが、中身はWebサービスを呼び出すための処理になっている。いろいろできているが、いくつかピックアップして見てみる。クラスの仕様は前回のWebサービスに準じているので、そちらの記事を参照のこと。
- Calculator.java
package web; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.ws.Action; import javax.xml.ws.RequestWrapper; import javax.xml.ws.ResponseWrapper; /** * This class was generated by the JAX-WS RI. * JAX-WS RI 2.2.10 * Generated source version: 2.2 * */ @WebService(name = "Calculator", targetNamespace = "http://web/") @XmlSeeAlso({ ObjectFactory.class }) public interface Calculator { /** * * @param arg0 * @return * returns int */ @WebMethod @WebResult(targetNamespace = "") @RequestWrapper(localName = "sub", targetNamespace = "http://web/", className = "web.Sub") @ResponseWrapper(localName = "subResponse", targetNamespace = "http://web/", className = "web.SubResponse") @Action(input = "http://web/Calculator/subRequest", output = "http://web/Calculator/subResponse") public int sub( @WebParam(name = "arg0", targetNamespace = "") Calc arg0); /** * * @param arg0 * @return * returns int */ @WebMethod @WebResult(targetNamespace = "") @RequestWrapper(localName = "add", targetNamespace = "http://web/", className = "web.Add") @ResponseWrapper(localName = "addResponse", targetNamespace = "http://web/", className = "web.AddResponse") @Action(input = "http://web/Calculator/addRequest", output = "http://web/Calculator/addResponse") public int add( @WebParam(name = "arg0", targetNamespace = "") Calc arg0); }
- Calc.java
package web; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlType; /** * <p>Java class for calc complex type. * * <p>The following schema fragment specifies the expected content contained within this class. * * <pre> * <complexType name="calc"> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <sequence> * <element name="num1" type="{http://www.w3.org/2001/XMLSchema}int"/> * <element name="num2" type="{http://www.w3.org/2001/XMLSchema}int"/> * </sequence> * </restriction> * </complexContent> * </complexType> * </pre> * * */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "calc", propOrder = { "num1", "num2" }) public class Calc { protected int num1; protected int num2; /** * Gets the value of the num1 property. * */ public int getNum1() { return num1; } /** * Sets the value of the num1 property. * */ public void setNum1(int value) { this.num1 = value; } /** * Gets the value of the num2 property. * */ public int getNum2() { return num2; } /** * Sets the value of the num2 property. * */ public void setNum2(int value) { this.num2 = value; } }
Webサービス公開時には作っていなかったクラスの一つとして、CalculatorServiceというクラスができている。これは公開した<サービス名> + Serviceという名前になっていて、Webサービスに対するエンドポイントを保持している。実際の使い方としては、このCalcuratorServiceクラスからCalcuratorオブジェクトを取得してサービスを呼び出すことになる。
- CalculatorService.java
package web; import java.net.MalformedURLException; import java.net.URL; import javax.xml.namespace.QName; import javax.xml.ws.Service; import javax.xml.ws.WebEndpoint; import javax.xml.ws.WebServiceClient; import javax.xml.ws.WebServiceException; import javax.xml.ws.WebServiceFeature; /** * This class was generated by the JAX-WS RI. * JAX-WS RI 2.2.10 * Generated source version: 2.2 * */ @WebServiceClient(name = "CalculatorService", targetNamespace = "http://web/", wsdlLocation = "http://vmora7:7001/SoapApp/CalculatorService?wsdl") public class CalculatorService extends Service { private final static URL CALCULATORSERVICE_WSDL_LOCATION; private final static WebServiceException CALCULATORSERVICE_EXCEPTION; private final static QName CALCULATORSERVICE_QNAME = new QName("http://web/", "CalculatorService"); static { URL url = null; WebServiceException e = null; try { url = new URL("http://vmora7:7001/SoapApp/CalculatorService?wsdl"); } catch (MalformedURLException ex) { e = new WebServiceException(ex); } CALCULATORSERVICE_WSDL_LOCATION = url; CALCULATORSERVICE_EXCEPTION = e; } public CalculatorService() { super(__getWsdlLocation(), CALCULATORSERVICE_QNAME); } public CalculatorService(WebServiceFeature... features) { super(__getWsdlLocation(), CALCULATORSERVICE_QNAME, features); } public CalculatorService(URL wsdlLocation) { super(wsdlLocation, CALCULATORSERVICE_QNAME); } public CalculatorService(URL wsdlLocation, WebServiceFeature... features) { super(wsdlLocation, CALCULATORSERVICE_QNAME, features); } public CalculatorService(URL wsdlLocation, QName serviceName) { super(wsdlLocation, serviceName); } public CalculatorService(URL wsdlLocation, QName serviceName, WebServiceFeature... features) { super(wsdlLocation, serviceName, features); } /** * * @return * returns Calculator */ @WebEndpoint(name = "CalculatorPort") public Calculator getCalculatorPort() { return super.getPort(new QName("http://web/", "CalculatorPort"), Calculator.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 Calculator */ @WebEndpoint(name = "CalculatorPort") public Calculator getCalculatorPort(WebServiceFeature... features) { return super.getPort(new QName("http://web/", "CalculatorPort"), Calculator.class, features); } private static URL __getWsdlLocation() { if (CALCULATORSERVICE_EXCEPTION!= null) { throw CALCULATORSERVICE_EXCEPTION; } return CALCULATORSERVICE_WSDL_LOCATION; } }
生成したコードを使ってSOAPサービスを呼び出す
生成されたコードを使ってSOAPサービスを呼び出してみる。以下のようなmainメソッドを持つクラスを作成し、実行する。
- Main.java
package client; import web.Calc; import web.Calculator; import web.CalculatorService; public class Main { public static void main(String[] args) { CalculatorService service = new CalculatorService(); Calculator calculator = service.getCalculatorPort(); Calc calc = new Calc(); calc.setNum1(7); calc.setNum2(3); int addResult = calculator.add(calc); int subResult = calculator.sub(calc); System.out.println("add=" + addResult); System.out.println("sub=" + subResult); } }
- 実行結果
add=10 sub=4
正しく計算結果が取得できている!これが、ローカルで計算しているのではなく、間違いなくWebサービスに問い合わせていることを確認するために、アプリケーションを停止して、Webサービスを止めた状態で再度実行してみる。
Exception in thread "main" javax.xml.ws.WebServiceException: Failed to access the WSDL at: http://vmora7:7001/SoapApp/CalculatorService?wsdl. It failed with: Got Operation timed out while opening stream from http://vmora7:7001/SoapApp/CalculatorService?wsdl. at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.tryWithMex(RuntimeWSDLParser.java:173) at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:155) at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:120) at com.sun.xml.internal.ws.client.WSServiceDelegate.parseWSDL(WSServiceDelegate.java:258) at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:221) at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:169) at com.sun.xml.internal.ws.spi.ProviderImpl.createServiceDelegate(ProviderImpl.java:101) at javax.xml.ws.Service.<init>(Service.java:77) at web.CalculatorService.<init>(CalculatorService.java:42) at client.Main.main(Main.java:9) Caused by: java.io.IOException: Got Operation timed out while opening stream from http://vmora7:7001/SoapApp/CalculatorService?wsdl at ...(略)
今度はWebサービスのエンドポイントに接続できずにエラーとなった。このことから、先ほどは正しくWebサービスを呼び出しているよう!
まとめ
前回に簡単なクラスからWebサービスを公開し、今回はMetroというツールを用いてそのWebサービスのWSDLのみからクライアント側のソースコードを簡単に作成できることを検証した。WSDLやSOAPの仕様は複雑だが、複雑な部分はアノテーションや周辺ツール、サーバ側でいいかんじに隠蔽されており、使う分には割とシンプルに扱えることが分かった!
以上。