내용 목차
본 장에서는 웹 서비스 호출 방식에 대해서 알아보고 예제를 통해 실제 구현 방식을 설명한다.
하나의 JAX-WS 웹 서비스 클라이언트 애플리케이션은 원격의 웹 서비스 Endpoint에 다음의 두 가지 방식으로 접근할 수 있다.
동적 프록시(Dynamic Proxy) 방식
디스패치(Dispatch) 방식
동적 프록시(Dynamic Proxy) 방식의 웹 서비스 호출은 크게 JavaSE 방식과 JavaEE 방식으로 나뉜다. 이 두 가지 방식은 모두 공통적으로 클라이언트 아티팩트들을 생성하는 것을 전제로 하고 있다.
본 장에서는 우선 이러한 클라이언트 아티팩트들을 어떻게 생성하는지에 대해 살펴보고, JavaSE 방식 그리고 JavaEE 방식의 웹 서비스 호출에 대해 본격적으로 살펴보기로 한다.
웹 서비스의 클라이언트 호출은 그 서비스가 Java 클래스로부터 만들어지든 WSDL 파일로부터 만들어지든 결국 그 서비스가 퍼블리쉬하는 WSDL 파일을 이용하여 클라이언트 아티팩트들을 생성한 후 호출하는 Java 클래스의 작성을 통해 이루어진다. 따라서 본 절에서는 간단히 앞 장에서 Java 클래스를 통해 생성한 웹 서비스를 호출하는 클라이언트를 위한 클라이언트 아티팩트들을 생성해 본다.
웹 서비스의 클라이언트 아티팩트(프록시)들은 그 서비스가 제공하는 WSDL로부터 JEUS 6 웹 서비스에서 제공하는 툴인 wsimport를 통해 얻어진 서비스 인터페이스 클래스들과 서비스 Endpoint 인터페이스 클래스로 이루어진다.
콘솔에서 wsimport를 이용해서 클라이언트 아티팩트들을 얻는 방법은 다음과 같다.
C:\>wsimport -help
Usage: wsimport [options] <WSDL_URI>
where [options] include:
-b <path> specify external jaxws or jaxb binding
files (Each <file> must have its own -b)
-catalog <file> specify catalog file to resolve external
entity references supports TR9401,
XCatalog, and OASIS XML Catalog format.
-d <directory> specify where to place generated output
files
-extension allow vendor extensions - functionality
not specified by the specification. Use
of extensions may result in applications
that are not portable or may not
interoperate with other implementations
-help display help
-httpproxy:<host>:<port> specify a HTTP proxy server (port defaults
to 8080)
-keep keep generated files
-p <pkg> specifies the target package
-quiet suppress wsimport output
-s <directory> specify where to place generated source
files
-target <version> generate code as per the given JAXWS
specification version. version 2.0 will
generate compliant code for JAXWS 2.0 spec.
-verbose output messages about what the compiler is
doing
-version print version information
-wsdllocation <location> @WebServiceClient.wsdlLocation value
Examples:
wsimport stock.wsdl -b stock.xml -b stock.xjb
wsimport -d generated http://example.org/stock?wsdl
JEUS 6 웹 서비스는 wsimport의 Ant Task도 지원하는데 보다 자세한 콘솔 명령에 대한 설명과 Ant Task 항목에 대한 설명은 부록을 참조한다.
여기서는 다음과 같은 원격 웹 서비스의 WSDL 파일을 가지고 그 아래의 wsimport의 Ant Task를 이용하여 Portable Artifact들을 생성한다.
[예 5.1] << http://host:port/AddNumbers/AddNumbersImplService?wsdl >>
<?xml version="1.0" encoding="UTF-8"?> <definitions targetNamespace="http://server.fromjava/" name="AddNumbersImplService" xmlns:tns="http://server.fromjava/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns="http://schemas.xmlsoap.org/wsdl/"> <types> <xsd:schema> <xsd:import namespace="http://server.fromjava/" schemaLocation="AddNumbersImplService_schema1.xsd" /> </xsd:schema> </types> <message name="addNumbers"> <part name="parameters" element="tns:addNumbers" /> </message> <message name="addNumbersResponse"> <part name="parameters" element="tns:addNumbersResponse" /> </message> <portType name="AddNumbersImpl"> <operation name="addNumbers"> <input message="tns:addNumbers" /> <output message="tns:addNumbersResponse" /> </operation> </portType> <binding name="AddNumbersImplPortBinding" type="tns:AddNumbersImpl"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="addNumbers"> <soap:operation soapAction="" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> </binding> <service name="AddNumbersImplService"> <port name="AddNumbersImplPort" binding="tns:AddNumbersImplPortBinding"> <soap:address location="REPLACE_WITH_ACTUAL_URL" /> </port> </service> </definitions>
[예 5.2] << build.xml >>
... <target name="build_client" depends="do-deploy-success, init"> <antcall target="wsimport"> <param name="package.name" value="fromjava.client" /> <param name="binding.file" value="" /> <param name="wsdl.file" value="http://localhost:8088/AddNumbers/AddNumbersImplService?wsdl" /> </antcall> <antcall target="do-compile"> <param name="javac.excludes" value="fromjava/server/" /> </antcall> </target> ...
위의 wsimport Ant Task를 가지고 호출할 웹 서비스의 WSDL 파일을 이용하여 Portable Artifact들을 생성하면 그 결과로 생성되는 파일들은 다음과 같다.
fromwsdl/client/AddNumbers.class fromwsdl/client/AddNumbersImpl.class fromwsdl/client/AddNumbersImplService.class fromwsdl/client/AddNumbersResponse.class fromwsdl/client/ObjectFactory.class fromwsdl/client/package-info.class
AddNumbers와 AddNumbersResponse 파일들은 각각 JAXB가 addNumbers 메소드에 대한 요청과 응답을 Java 오브젝트로 혹은 XML 문서로 변환하기 위해 사용하는 Java Bean이다. 그리고 AddNumbersImpl 파일은 서비스 Endpoint 인터페이스를 나타내며 AddNumbersImplService 파일은 클라이언트에서 프록시의 용도로 사용하는 서비스 인터페이스의 Java 클래스이다. 그리고 나머지 ObjectFactory 클래스와 package-info 클래스는 JAXB가 생성한 파일들이다. 이러한 각각의 파일들의 내용은 다음과 같다.
다음은 wsimport 툴을 이용하여 위의 원격 웹 서비스의 WSDL 파일로부터 얻은 서비스 인터페이스 클래스인 AddNumbersImplService 클래스이다.
[예 5.3] << AddNumbersImplService.java >>
@WebServiceClient(name = "AddNumbersImplService", targetNamespace = "http://server.fromjava/", wsdlLocation = "http://localhost:8088/AddNumbers/AddNumbersImplService?wsdl") public class AddNumbersImplService extends Service { private final static URL ADDNUMBERSIMPLSERVICE_WSDL_LOCATION; static { URL url = null; try { url = new URL( "http://localhost:8088/AddNumbers/AddNumbersImplService?wsdl"); } catch (MalformedURLException e) { e.printStackTrace(); } ADDNUMBERSIMPLSERVICE_WSDL_LOCATION = url; } public AddNumbersImplService(URL wsdlLocation, QName serviceName) { super(wsdlLocation, serviceName); } public AddNumbersImplService() { super(ADDNUMBERSIMPLSERVICE_WSDL_LOCATION, new QName( "http://server.fromjava/", "AddNumbersImplService")); } @WebEndpoint(name = "AddNumbersImplPort") public AddNumbersImpl getAddNumbersImplPort() { return (AddNumbersImpl) super.getPort(new QName( "http://server.fromjava/", "AddNumbersImplPort"), AddNumbersImpl.class); } @WebEndpoint(name = "AddNumbersImplPort") public AddNumbersImpl getAddNumbersImplPort(WebServiceFeature... features) { return (AddNumbersImpl) super.getPort(new QName( "http://server.fromjava/", "AddNumbersImplPort"), AddNumbersImpl.class, features); } }
위의 서비스 인터페이스는 클라이언트에서 실제 프록시 객체를 얻을 때 사용한다. 클라이언트에서는 위의 클래스에서 AddNumbersImplService 생성자를 통해 얻은 AddNumbersImplService 객체로부터 getAddNumbersImplPort() 메소드를 통해 서비스 Endpoint 인터페이스인 AddNumbersImpl을 얻을 수 있다.
다음은 wsimport 툴을 이용하여 위의 원격 웹 서비스의 WSDL 파일로부터 얻은 서비스 Endpoint 인터페이스인 AddNumbersImpl 클래스이다.
[예 5.4] << AddNumbersImpl.java >>
@WebService(name = "AddNumbersImpl", targetNamespace = "http://server.fromjava/") @XmlSeeAlso( { ObjectFactory.class }) public interface AddNumbersImpl { @WebMethod @WebResult(targetNamespace = "") @RequestWrapper(localName = "addNumbers", targetNamespace = "http://server.fromjava/", className = "fromjava.client.AddNumbers") @ResponseWrapper(localName = "addNumbersResponse", targetNamespace = "http://server.fromjava/", className = "fromjava.client.AddNumbersResponse") public int addNumbers(@WebParam(name = "arg0", targetNamespace = "") int arg0, @WebParam(name = "arg1", targetNamespace = "") int arg1); }
위와 같이 생성된 서비스 Endpoint 인터페이스는 동적 바인딩(dynamic binding) 혹은 런타임에 동작하는 Java 오브젝트로 혹은 XML 문서로의 변환을 위한 Annotation들을 가지고 있다(이 서비스 Endpoint 인터페이스는 다시 WSDL과 스키마 파일들을 생성하는데 사용될 수 있으나 이 서비스 Endpoint 인터페이스를 얻기 위해 사용된 WSDL이나 스키마 파일과 일치하지 않을 수 있다).
또한 JAXB가 addNumbers 메소드에 대한 요청과 응답을 Java 오브젝트로 혹은 XML 문서로 변환하기 위해 사용하는 Java Bean 클래스인 AddNumbers 클래스와 AddNumbersResponse 클래스는 다음과 같다.
[예 5.5] << AddNumbers.java >>
@XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "addNumbers", propOrder = { "arg0", "arg1" }) public class AddNumbers { protected int arg0; protected int arg1; public int getArg0() { return arg0; } public void setArg0(int value) { this.arg0 = value; } public int getArg1() { return arg1; } public void setArg1(int value) { this.arg1 = value; } }
[예 5.6] << AddNumbersResponse.java >>
@XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "addNumbersResponse", propOrder = { "_return" }) public class AddNumbersResponse { @XmlElement(name = "return") protected int _return; public int getReturn() { return _return; } public void setReturn(int value) { this._return = value; } }
이 2개의 Java Bean 클래스는 원격 웹 서비스의 WSDL 파일의 message element들인
AddNumbers, AddNumbersResponse에 해당하는 것을 알 수 있다.
본 절에서는 “5.2.1. 클라이언트 아티팩트 생성”에서 얻은 클라이언트 Portable Artifact들을 가지고 실제로 원격의 웹 서비스를 호출하는 로직을 담고 있는 클라이언트 클래스를 JavaSE 방식으로 작성하고 호출해 본다.
JavaSE 클라이언트 클래스를 작성하는 것은 매우 간단하다. 위에서 얻은 서비스 인터페이스인 AddNumbersImplService 객체를 하나 생성하고 그로부터 서비스 Endpoint 인터페이스인 AddNumbersImpl을 구현한 객체를 얻는다. 실제로 이 객체는 직접 동적 프록시를 통해 원격의 웹 서비스를 호출하는 로직을 담고 있다. 이렇게 서비스 Endpoint 인터페이스를 구현하는 객체를 얻었으면 이로부터 실제의 웹 서비스를 호출하는 메소드를 호출한다.
다음은 위의 wsimport Ant Task를 통해 얻은 Portable Artifact들을 가지고 원격의 웹 서비스를 호출하는 코드의 일부분이다.
[예 5.7] << AddNumbersClient.java >>
public class AddNumbersClient { public static void main(String[] args) { AddNumbersImpl port = new AddNumbersImplService() .getAddNumbersImplPort(); int number1 = 10; int number2 = 20; System.out.println("##############################################"); System.out.println("### JAX-WS Webservices examples - fromjava ###"); System.out.println("##############################################"); System.out.println("Invoking addNumbers(" + number1 + ", " + number2 + ")"); int result = port.addNumbers(number1, number2); System.out.println("Result: " + result); } }
본 절에서는 지금까지 구현한 클래스들 및 기타 설정 파일들을 가지고 웹 서비스를 구현하여 JEUS 6에 디플로이한 후 클라이언트를 실행하는 방법을 알아보기로 한다.
원격의 웹 서비스에 접근하는 클라이언트 프로그램의 호출 방식은 그 서비스가 Java로부터 구현하거나 WSDL로부터 구현하거나 동일하다.
다음과 같이 Java로부터 구현한다면 fromjava 디렉터리에서, WSDL 파일로부터 구현한다면 fromwsdl 디렉터리에서 서비스를 생성하여 JEUS 6에 디플로이 한다.
C:\>ant build deploy
위의 과정이 모두 실행되어 서비스가 정상적으로 디플로이 되었으면, 클라이언트를 빌드한다. JavaSE 클라이언트에서 wsimport의 과정을 거치므로 서비스의 디플로이가 모두 끝마쳤을 때 클라이언트의 구성이 가능하다.
다음과 같이 클라이언트를 생성하고 서비스를 호출한다. 다음과 같이 콘솔에서 입력하면 클라이언트가 정상적으로 이 서비스를 호출하고 원하는 값을 얻었음을 알 수 있다.
C:\>ant run ... run: [java] ################################################## [java] ##### JAX-WS Webservices examples - fromjava ##### [java] ################################################## [java] Invoking addNumbers(10, 20) [java] Result: 30 ... BUILD SUCCESSFUL
본 절에서는 간단히 앞 장에서 WSDL 문서를 통해 생성한 웹 서비스를 호출하는 클라이언트를 JavaEE 방식으로 작성하고 호출해본다.
시작하기 전에 우선“5.2.1. 클라이언트 아티팩트 생성”에서 설명한 방식으로 클라이언트 Portable Artifact들을 얻은 것을 가정한다.
JavaEE 클라이언트 방식에서 중요한 점은 기존 JAX-RPC 환경의 J2EE 웹 서비스 클라이언트 구성에서는 서블릿일 경우 web.xml에 EJB일 경우에는 ejb-jar.xml에 service-ref element를 추가하여 웹 서비스 클라이언트 프록시 생성에 필요한 정보를 JNDI에 등록할 수 있었으나 JAX-WS 웹 서비스 환경에서의 JavaEE 클라이언트 구성은 @WebServiceRef Annotation을 설정함으로써 그와 동일한 작업이 가능하다는 것이다.
다음은 @WebServiceRef Annotation을 설정하는 하나의 예이다.
... @WebServiceRef(wsdlLocation="http://host:port/TmaxService/TmaxService?wsdl) static TmaxServiceImplService tsvc; ...
위에서 얻은 서비스 인터페이스인 AddNumbersService 타입을 이용하여 클라이언트 Java 클래스의 멤버 변수로 @WebServiceRef Annotation과 함께 선언한다. 그러면 이 멤버 변수는 런타임 중 클라이언트 클래스가 초기화될 때 실제 set 메소드가 없어도 서블릿일 경우 서블릿 컨테이너, EJB일 경우 EJB 컨테이너에서 자동으로 이 값을 인젝션(injection)한다.
이로부터 서비스 Endpoint 인터페이스인 AddNumbersPortType을 구현한 객체를 얻는다. 실제로 이 객체는 직접 동적 프록시를 통해 원격의 웹 서비스를 호출하는 로직을 담고 있다. 이렇게 서비스 Endpoint 인터페이스를 구현하는 객체를 얻었으면 이로부터 실제의 웹 서비스를 호출하는 메소드를 호출한다.
다음은 위의 wsimport Ant Task를 통해 얻은 Portable Artifact들을 가지고 원격의 웹 서비스를 호출하는 클라이언트 프로그램이다.
[예 5.8] << AddNumbersClient.java >>
public class AddNumbersClient extends HttpServlet { @WebServiceRef static AddNumbersService svc; protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException { AddNumbersPortType port = svc.getAddNumbersPort(); int number1 = 10; int number2 = 20; System.out.println("##############################################"); System.out.println("### JAX-WS Webservices examples - fromjava ###"); System.out.println("##############################################"); System.out.println("Invoking addNumbers(" + number1 + ", " + number2 + ")"); int result = port.addNumbers(number1, number2); System.out.println("##############################################"); System.out.println("### JAX-WS Webservices examples - fromjava ###"); System.out.println("##############################################"); System.out.println("Result: " + result); } }
본 절에서는 지금까지 구현한 클래스들 및 기타 설정 파일들을 가지고 웹 서비스를 구현하여 JEUS 6에 디플로이한 후 JavaEE 클라이언트를 실행하는 방법을 알아보기로 한다.
원격의 웹 서비스에 접근하는 클라이언트 프로그램의 호출 방식은 그 서비스가 Java로부터 구현하거나, WSDL로부터 구현하거나 동일하다.
다음과 같이 Java로부터 구현한다면 fromjava 디렉터리에서, WSDL 파일로부터 구현한다면 fromwsdl 디렉터리에서 서비스를 생성하여 JEUS 6에 디플로이한다.
C:\>ant build deploy
위의 과정이 모두 실행되어 서비스가 정상적으로 디플로이되면, JavaEE 클라이언트를 빌드한다. JavaEE 클라이언트에서 wsimport의 과정을 거치므로 서비스의 디플로이가 모두 완료되었을 때 클라이언트의 구성이 가능하다.
다음과 같이 클라이언트를 생성하고 호출한다. 다음과 같이 콘솔에서 입력하면 웹 브라우저를 통해 클라이언트 서블릿이 정상적으로 이 서비스를 호출하고 원하는 값을 서버상의 로그를 통해 알 수 있다.
C:\>ant run ... ############################################## ### JAX-WS Webservices examples - fromjava ### ############################################## Invoking addNumbers(10, 20) ... ############################################## ### JAX-WS Webservices examples - fromjava ### ############################################## Result: 30 ...
디스패치(Dispatch) 방식의 웹 서비스 호출은 XML 구성을 java.lang.transform.Source 혹은 javax.xml.soap.SOAPMessage, 즉 XML 수준에서 다루는 것을 선호하는 개발자들을 위한 것이다. 디스패치 방식의 웹 서비스 호출은 메시지 모드와 페이로드(Payload) 모드로 사용될 수 있고 XML/HTTP 바인딩(javax.activation.DataSource)으로 레스트(REST) 웹 서비스를 생성할 때 사용될 수 있다.
이는 “제8장 프로바이더와 디스패치 인터페이스”에서 자세하게 설명한다.