1. 웹 서비스 호출 모델
1.1 웹 서비스 클라이언트
JAX-RPC API를 이용해서 웹 서비스 서버 부분을 개발하고 웹 컨테이너에 설치를 마쳤으면, 이제부터는 이를 이용할 웹 서비스 클라이언트 부분의 개발에 대해서 살펴보자.
JAX-RPC API를 사용한 웹 서비스 클라이언트 부분 개발은 다음과 같은 순서로 작성된다.
1. 스텁 파일을 생성한다. 단 동적 서비스 호출에서는 생략된다.
클라이언트 프로그램을 작성하기 위해 웹 서비스 스텁을 생성한다. 서비스의 구현을 remote 인터페이스를 구현한 자바 객체로 보기 때문에 이를 호출하는 스텁을 생성하는 것은 자바 프로그래밍 입장에서 보면 자연스러운 과정일 것이다.
2. 클라이언트 프로그램을 작성한다.
3. 클라이언트 프로그램을 컴파일한다.
4. 클라이언트 프로그램을 실행한다.
1.2 웹 서비스 호출 모델
웹 서비스 시스템을 이용하는 웹 서비스 클라이언트를 JAX-RPC로 개발할 경우, JAX-RPC는 용도에 따라 다음과 같은 방식의 웹 서비스 호출 API를 제공한다.
⑴ 정적 서비스 호출 : 정적 스텁 호출 모델
WSDL 문서를 이용해서 수동으로 생성된 스텁 클래스를 정적 스텁(Static Stub)이라고 하는데, 이렇게 생성된 스텁을 이용해서 원격 프로시저를 호출하는 모델이다.
⑵ 동적 서비스 호출
① 동적 프록시 호출 모델
동적 프록시(Dynamic Proxy)란 동적으로 생성되는 프록시를 말한다. 일반적으로 프록시는 외부 인터넷과 내부 컴퓨터 간에 중개 역할을 하는 소프트웨어를 의미하는데, JAX-RPC에서는 웹 서비스 시스템과 웹 서비스 클라이언트를 서로 연결해 주는 스텁 클래스를 의미한다. 따라서 동적 프록시는 동적 스텁이라고 말할 수 있다.
동적 프록시 호출 모델 역시 WSDL 문서가 필요하다는 점에서는 정적 호출 모델과 비슷하지만, 정적 모델처럼 수동으로 스텁 클래스를 생성하지 않고 실행 도중에 스텁 객체가 생성된다는 점이 다르다.
② 동적 호출 인터페이스 모델
동적 호출 인터페이스(Dynamic Invocation Interface)란 동적으로 호출 가능한 인터페이스라는 의미로서, 클라이언트 측에서 원격 인터페이스명을 알지 못해도 동적으로 원격 메소드를 호출할 수 있도록 해 주는 모델이다. Call 객체를 이용하면 원격 프로시저명과 인자에 대한 정보만 알고 있으면 원격 프로시저를 호출할 수 있다.
2. 정적 스텁 호출 모델
2.1 개념
웹 서비스를 사용하려고 할 때, 우리는 사용하려는 웹 서비스의 인터페이스(WSDL)를 우연히 알고 있을 수도 있고, 정확히 알지 못한 상태에서 UDDI와 같은 레지스트리 서버에서 웹 서비스의 인터페이스를 받아서 알게 될 수도 있다.
웹 서비스도 분산 컴퓨팅의 한 형태로서 원격으로 서비스를 호출하는 것도 오버헤드가 크다고 할 수 있는데 매번 웹 서비스를 레지스트리에서 찾고 동적으로 호출 메시지를 만들어 낸다면 우리가 원하는 만큼의 수행을 얻기 힘들 것이다. 물론 웹 서비스가 플랫폼 독립적이어야 하는 것은 당연하지만, 일단 원하는 인터페이스를 얻었고 그것을 제공하는 서버의 위치도 알았다면 최고의 성능을 위해 미리 호출할 메시지를 쓰기 좋게 변형해 놓는 것을 생각해 볼 수 있을 것이다. 또한 더 나아가 매번 XML 메시지를 프로그램에서 작성하지 않고 우리가 익숙한 자바 메소드 형태로 정의한다면 개발이 더욱 쉬워질 것이
다.
JAX-RPC에서 이렇게 확인된 웹 서비스 인터페이스를 자바 네이티브 방식으로 호출할 수 있는 방법을 제공하는데 이 방법을 정적 서비스 호출이라고 한다. 여기에는 우리가 CORBA나 RMI 프로그래밍하면서 전통적으로 사용해온 메커니즘이 사용되는데, 인터페이스를 기본으로 클라이언트가 사용할 스텁을 만들고 서버가 사용할 스켈레톤 및 기타 부수적인 파일을 생성한 후 클라이언트는 스텁과 인터페이스만 가지고 서버 프로그램을 호출할 수 있도록 하는 것이다.
다시 말해 정적 서비스 호출 모델은 수동으로 생성된 스텁을 이용해서 원격 프로시저를 호출하는 방식으로서, 웹 서비스 클라이언트 개발자는 WSDL 문서로부터 스텁 클래스를 생성하고, 스텁 클래스를 이용해서 클라이언트 클래스를 작성해야 한다. 따라서 클라이언트 클래스가 컴파일 및 실행되기 전에 반드시 스텁 클래스가 생성되어 있어야 한다.
위의 그림은 JAX-RPC 정적 서비스 호출 과정을 나타내는 것으로, 서버와 클라이언트 사이에는 HTTP를 통한 SOAP 메시지로 표준화되어 있고 서버와 클라이언트는 각각 자바 네이티브로 최적화되어 있다.
2.2 스텁 클래스 생성
스텁 클래스는 JAX-RPC에서 제공하는 wscompile.bat라는 도구를 사용해서 만든다. 이 도구는 스텁 클래스를 생성하기 위해서 config.xml 문서를 필요로 한다. 이 문서는 WSDL 문서를 다운로드 받을 수 있는 URL과 스텁 클래스가 포함할 패키지명이 기술된 XML 문서로서, 필요한 정보는 UDDI 레지스트리에서 검색을 통해서 얻어야 한다. 여기서 패키지명이라는 것은 웹 서비스 시스템의 서비스 정의 인터페이스에서 사용된 패키지의 이름을 의미한다.
config.xml 문서 작성을 위한 문법 <?xml version="1.0" encoding="euc-kr"?> <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <wsdl location="WSDL 문서를 다운로드 받을 수 있는 URL" packageName="인터페이스 패키지명" /> </configuration> |
참고: config.xml 문서의 구조를 정의하고 있는 XML 스키마 config.xml 문서의 자세한 구조를 이해하기위해서는 jax-rpc-ri-config.xsd 파일을 참조하면 된다. 여기서는 이 파일의 일부분만을 보여주고 있다. ...... 전략 ...... <xsd:element name="configuration"> <xsd:annotation> <xsd:documentation> The top-level element. It must contain one out of three possible elements, corresponding to three different ways to feed service information to the tool. Elements: (mutually exclusive) "service" - a service description based on a set of service endpoint interfaces; "wsdl" - a WSDL document to import and process; "modelfile" - a previously saved model file (-model option in wscompile). </xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:sequence> <xsd:choice> <xsd:element name="service" type="tns:serviceType"/> <xsd:element name="wsdl" type="tns:wsdlType"/> <xsd:element name="modelfile" type="tns:modelfileType"/> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> ...... 중략 ....... <xsd:complexType name="wsdlType"> <xsd:annotation> <xsd:documentation> A description of a service based on an existing WSDL document. Attributes: "location" - URL of the WSDL document; "packageName" - name of the Java package to use by default. Elements: "typeMappingRegistry"? - the type mapping registry to use for this service; "handlerChains"? - default handler chains for the endpoints in this service; "namespaceMappingRegistry"? - XML namespace to Java package mapping information. </xsd:documentation> </xsd:annotation> <xsd:sequence> <xsd:element name="typeMappingRegistry" type="tns:typeMappingRegistryType" minOccurs="0"/> <xsd:element name="handlerChains" type="tns:handlerChainsType" minOccurs="0"/> <xsd:element name="namespaceMappingRegistry" type="tns:namespaceMappingRegistryType" minOccurs="0"/> </xsd:sequence> <xsd:attribute name="location" type="xsd:anyURI" use="required"/> <xsd:attribute name="packageName" type="xsd:string" use="required"/> </xsd:complexType> ....... 후략 ....... |
지난 강의에서 살펴본 Hello 웹 서비스(sayHello() 메소드를 통해서 주어진 문자열에 문자열을 추가해서 반환하는 서비스)에 대해 클라이언트 프로그램을 어떻게 작성할 수 있는 지를 살펴보도록 하겠다. 우선 Hello 서비스에 대한 config.xml 문서는 다음과 같이 작성하면 된다.
config.xml 문서 작성의 예 <?xml version="1.0" encoding="euc-kr"?> <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/conf ig"> <wsdl location="http://localhost:8080/hello/webservice?WSDL" packageName="hello"/> </configuration> |
이렇게 작성된 문서를 가지고 Hello 웹 서비스 시스템과 바인딩하기 위한 스텁 클래스를 생성하기 위해서는 wscompile.bat을 이용한다. 성공적으로 실행되면 패키지 디렉토리와 스텁 클래스 및 관련 클래스가 생성된다.
2.3 클라이언트 프로그램 작성
다음은 정적 서비스 호출 방식의 클라이언트 프로그램(“HelloClient.java”)이다. 아래와 같이 HelloClient.java 코드를 작성한 후, 클라이언트 프로그램을 컴파일하고 성공적으로 실행되면 'Hello Everybody!'라는 메시지가 출력된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
HelloClient.java - 정적 서비스 호출을 지원하는 클라이언트 프로그램 package hello; import javax.xml.rpc.Stub; public class HelloClient { public static void main(String[] args) { try { Webservice_Impl webservice = new Webservice_Impl(); Stub stub = (Stub) webservice.getHelloIFPort(); HelloIF hello = (HelloIF) stub; System.out.println( hello.sayHello("Everybody") ); } catch (Exception ex) { ex.printStackTrace(); } } } |
8줄
웹 서비스 시스템의 구현 객체를 참조하는 객체 생성
클라이언트 프로그램에서 제일 먼저 생성해야 하는 객체는 웹 서비스 시스템의 구현 객체를 참조하는 객체이다. 이 객체를 만들기 위한 클래스는 스텁 클래스와 같이 생성되며, 클래스명은 WSDL 문서에서 <service> 엘리먼트의 name 속성값에 _Impl을 추가한 형태 즉, “(WSDL문서에서 service 엘리먼트의 name 속성값)_Impl.class”가 된다.
객체를 생성하기 위해서는 다음과 같이 디폴트 생성자를 이용한다.
클래스명 참조변수 = new 클래스명();
Hello 웹 서비스명이 "Webservice"이기 때문에 웹 서비스 시스템의 구현 객체를 참조하는 객체는 결국 다음과 같이 생성하면 된다.
Webservice_Impl webservice = new Webservice_Impl();
9줄
스텁 객체 얻기
이 부분에서 웹 서비스를 호출하기 위한 스텁 객체를 생성하는데, 이때 스텁 객체의 인스턴스를 만드는 역할은 xrpcc 컴파일러가 생성한 ‘Webservice_Impl’가 ‘getHelloIFPort' 메소드를 통해 하게 된다.
스텁 객체를 생성하기 위한 문법은 다음과 같다.
Stub stub = (Stub) 참조객체.get포트이름();
이 부분은 xrpcc 컴파일러가 웹 서비스 인터페이스 이름을 환경 파일에서 얻어서 만든 함수로서, 함수 이름은 'get' + '웹 서비스 인터페이스' + 'Port'가 된다.
따라서 Hello 웹 서비스의 경우에는 웹 서비스 인터페이스가 HelloIF이기 때문에 결국 스텁 객체를 얻어 내기 위한 코드는 다음과 같다.
Stub stub = (Stub) webservice.getHelloIFPort();
10줄
원격 인터페이스 객체로의 형 변환
원격 프로시저를 호출하기 위해서는 스텁 객체를 웹 서비스의 인터페이스 형으로 만들어야 한다. 따라서 다음과 같은 문법을 적용해서 Hello 웹 서비스 클라이언트에서 스텁 객체를 원격 인터페이스 객체로 형을 변환한다.
원격_인터페이스명 참조변수 = (원격_인터페이스명) 스텁객체;
HelloIF hello = (HelloIF) stub;
11줄
원격 프로시저 호출
원격 인터페이스에 명시된 원격 프로시저를 호출한다. HelloIF 형으로 변환된 스텁 객체에는 웹 서비스 인터페이스 HelloIF가 선언된 함수의 스텁뿐만 아니라 SOAP 호출을 위한 여러 가지가 구현되어 있다.
Hello 웹 서비스 클라이언트에서 실제로 sayHello() 함수를 호출하면 HelloIF형의 스텁에서는 함수 호출 내용을 SOAP 호출로 변환하여 HTTP를 통해 원격 웹 서비스로 전송한다.
웹 서비스와 클라이언트 간의 WSDL 문서의 상호작용에 대해서 살펴보자. 클라이언트는 어떻게 웹 서비스가 구현되었는지 알 필요가 없다. 어떤 언어가 쓰였는지, 어떤 플랫폼에서 작동하는 지를 또한 알 필요가 없다. WSDL 문서의 다른 부분은 자바 인터페이스와 클라이언트에게는 보여지지 않는 서비스의 자세한 부분을 구현한 자바 클래스를 생성하는 데 이용된다. 서비스가 개발 단계에서 만들어지기 때문에 이 모델을 정적 모델이라고 부르는 것이다.
만약 WSDL 문서 바뀌면 코드가 다시 바뀌어야 하며, 클라이언트 코드는 다시 컴파일되어 반영되어야 한다. 예를 들어 만약 새로운 기능이 서비스의 새로운 버전에 제공되면 새로운 자바 인터페이스와 스텁은 만들어져야 한다. 그래야만 클라이언트는 그것을 이용할 수 있고 이미 존재하는 기능들도 여전히 이용될 수 있다.
하지만 WSDL 문서가 바뀌었는지를 자동으로 알아내는 방법은 없다. 다시 말해 문서에 기술된 인터페이스와 컴파일된 자바 스텁이 일치하는 지를 자동으로 알아낼 수가 없다. 만약 서비스 제공자가 서비스를 갱신하더라도, 클라이언트는 변한 것을 적용하여 작업할 수도 있고 그렇지 않을 수도 있다. 만약 맞지 않는 변경을 하게 된다면 클라이언트는 실행 시 오류를 받게 될 것이다.
일반적으로 웹 서비스의 버전을 관리하는 것이 어떤 표준에서도 지원하지 않는다는 것이 사실이다. 웹 서비스의 요청 메시지는 버전에 관련된 숫자나 다른 어떤 것도 포함하고 있지 않다. 이것은 웹 서비스에 어떤 변화도 서비스의 사용자에게 혼란을 일으킬 것이다. 그래서 언제든지 가능하면 이러한 상황을 피하도록 신경을 써야 한다. 하지만 동적 서비스 호출에서는 이런 경우가 발생하지 않는다.
3. 동적 프록시 호출 모델
3.1 개념
일반적으로 웹 서비스 기술의 한 가지 장점은 응용 프로그램 간의 의사소통에서 자동화되는 부분을 증가시킬 수 있다는 것이다. 이것은 응용 프로그램들이 서로 간의 어떤 자세한 상황을 모르고서도 의사소통 통로를 구축할 수 있다는 것을 의미한다. 결국이것은 시스템이나 서비스가 바뀌어도 시스템의 사용자에게 변경을 요구하지 않아도 된다는 것을 의미한다.
UDDI 레지스트리는 다양한 WSDL 문서로 표현되는 비지니스 정보를 담고 있다. 이것은 레지스트리를 사용하는 응용 프로그램이 동적으로 서비스를 찾고 사용할 수 있게 해 준다. 주어진 목표는 정적 서비스 호출 모델에서와 같이 클라이언트가 인터페이스와 스텁 클래스의 코드는 바꾸지 않고 서비스를 이용하는 방법이 필요하다.
클라이언트는 어떤 서비스를 호출할 지, 그 서비스에 대한 WSDL에 정의된 프로토콜 중에 어떤 것을 선택할 지 결정을 내릴 수 있어야 한다. 만약 WSDL 문서가 바뀐다면 클라이언트는 클라이언트 코드를 다시 컴파일하지 않고 변경을 받아들일 수 있어야 한다. 이와 같은 것을 가능하게 하는 방식이 바로 동적 서비스 호출 방식이다.
앞에서 살펴 본 정적 서비스 호출과 달리 JAX-RPC는 2가지의 클라이언트 동적 호출 방식을 제공한다.
그중의 하나인 동적 프록시 호출 모델은 어떠한 특정 서비스 인터페이스를 알고 있을 때 이를 구현한 서비스의 종점을 동적으로 호출할 수 있도록 한다. 정적 호출 방법과 달리 인터페이스만 알고 있으면 이를 구현한 서비스의 소스 코드를 다시 컴파일하지 않아도 동적으로 런타임시 호출할 수 있다. 예를 들어, 여러 영화 예매 서비스 회사가 공통적이고 기본적인 예매 인터페이스를 통해 영화 예매 서비스를 구현하는 경우, 동적 프록시 방법을 이용하면 구현 내용에 관계없이 공통 인터페이스만 구현하면 모든 예매 서비스를 이용할 수 있다. WSDL 파일의 위치만 변경하면 코드 변경 없이 모든 서비스를 이용할
수 있다.
아래의 그림은 JAX-RPC 동적 프록시 호출 과정을 나타내고 있다.
3.2 클라이언트 프로그램 작성
동적 프록시 방식의 예제(“HelloClinet.java”)를 살펴보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
HelloClient.java - 동적 프록시 호출 방식의 클라이언트 프로그램 package hello; import javax.xml.rpc.*; import javax.xml.namespace.*; import java.net.*; public class HelloClient { public static void main(String[] args) { try { ServiceFactory serviceFactory = ServiceFactory.newInstance(); URL wsdlUrl = new URL("http://localhost:8080/hello/webservice?WSDL"); QName serviceQName = new QName( "http://localhost:8080/hello/webservice/wsdl/webservice", "Webservice"); Service service = serviceFactory.createService(wsdlUrl, serviceQName); QName portQName = new QName( "http://localhost:8080/hello/webservice/wsdl/webservice","HelloIFPort"); Stub stub = (Stub) service.getPort(portQName, hello.HelloIF.class); HelloIF hello = (HelloIF) stub; System.out.println(hello.sayHello("Everybody")); } catch (Exception ex) { ex.printStackTrace(); } } } |
위의 코드를 살펴보면 ServiceFactory 클래스의 createService 메소드를 통해 새로운 서비스 종점에 대한 동적 프록시를 생성하였다. 이 인터페이스를 통해 원격 웹 서비스를 동적으로 호출할 수 있는 것이다. getPort 메소드는 WSDL에 기술된 서비스 중 사용자가 사용하고자 하는 포트를 선택하도록 한다.
이렇게 함으로써 WSDL의 위치만 바꿔주면 코드를 다시 컴파일하지 않고도 다양한 서비스를 이용할 수 있게 된다.
10줄
ServiceFactory 객체 생성
클라이언트 프로그램에서 제일 먼저 작성해야 할 것은 javax.xml.rpc 패키지의 ServiceFactory 클래스로 부터 ServiceFactory 객체를 생성하는 것이다. 이 객체는 웹 서비스 시스템에 대한 정보를 담고 있는 Service 객체를 만드는 역할을 한다.
11-14줄
Service 객체 생성
접근하려는 웹 서비스 시스템의 구현 객체를 참조하는 javax.xml.rpc.Service 인터페이스형의 객체를 생성해야 한다. Service 객체를 생성하기 위해서는 ServiceFactory 클래스의 createService 함수의 인자로 웹 서비스 WSDL 주소와 네임스페이스 정보를 넘겨 WSDL에서 서비스 정보를 추출한다.
다음은 Service 객체를 생성하기 위한 문법이다.
Service service = serviceFactory.createService ( "첫번째 인자", "두번째 인자");
첫번째 인자 | WSDL 문서를 다운로드 받을 수 있는 URL을 java.net.URL 객체 형태로 주면된다. URL 객체 생성 방법 URL wsdlURL = new URL ("WSDL 문서를 다운로드 받을 수 있는 URL"); Hello 웹 서비스의 경우에는 다음과 같이 작성할 수 있다. URL wsdlURL = new URL("http://localhost:8080/hello/webservice?WSDL"); |
두번째 인자 | 웹 서비스 QName 객체로서, 웹 서비스명을 javax.xml.namespace.QName 객체 형태로 주어야 한다. 웹 서비스 시스템명을 QName 객체로 만드는 방법 QName serviveQName = new QName ("웹 서비스 네임스페이스명","웹 서비스명"); 웹 서비스 네임스페이스명과 웹 서비스명은 WSDL 문서에 언급되어 있는데, WSDL 문서에서 루트 엘리먼트인 <definitions> 엘리먼트의 targetNamespace 속성에 언급된 것이 웹 서비스 네임스페이스명이고, <service> 엘리먼트의 name 속성에 언급되어 있는 것이 웹 서비스명이 된다. Hello 웹 서비스의 경우에는 다음과 같이 작성할 수 있다. QName serviceQName = new QName( "http://localhost:8080/hello/webservice/wsdl/webservice", "Webservice"); |
15~17줄
스텁 객체 생성
Service 클래스의 getPort 함수를 통해 원격 인터페이스 클래스 타입을 참조한 동적 프록시 객체를 생성한다. 즉 웹 서비스 시스템과 통신할 스텁 객체를 생성하려면 웹 서비스 시스템 구현 객체를 참조하는 Service 객체의 getPort 함수를 사용하면 된다.
getPort 메소드를 사용하는 규칙은 다음과 같다.
Stub stub = (Stub) service.getPort ("첫번째 인자", "두번째 인자");
첫번째 인자 | 포트명을 javax.xml.namespace.QName 객체 형태로 제공한다. 포트 QName 객체 생성 방법 QName PortQName = new QName ("포트 네임스페이스명", "포트명"); 포트 네임스페이스명과 포트명은 WSDL 문서에 언급되어 있는데, WSDL 문서에서 루트 엘리먼트인 <definitions> 엘리먼트의 targetNamespace 속성에 언급된 것이 포트 네임스페이스명이고, <port> 엘리먼트의 name 속성에 언급되어 있는 것이 포트명이 된다. Hello 웹 서비스의 경우에는 다음과 같이 작성할 수 있다. QName portQName = new QName( "http://localhost:8080/hello/webservice/wsdl/webservice","HelloIFPort"); |
두번째 인자 | 원격 인터페이스 클래스명을 나타내는 것으로, Hello 웹 서비스의 경우에는 "hello.HelloIF.class"가 된다. |
18-19줄
원격 인터페이스 객체로의 형 변환 및 원격 프로시저 호출
위에서 생성된 프록시 객체를 원하는 원격 인터페이스 객체로 형을 변환시킴으로써 웹 서비스 인터페이스에 선언된 함수를 호출할 수 있게 된다.Hello 웹 서비스 클라이언트의 경우에는 스텁 객체를 HelloIF로 형을 변환하고 sayHello() 메소드를 호출하면 된다.
위와 같이 작성된 동적 프록시 방식의 클라이언트 프로그램을 컴파일하기 위해서는 클라이언트 프로그램 자체는 물론이고 원격 인터페이스 소스 또는 바이트 코드가 필요하며, 이것들을 컴파일한 후 실행하면 원하는 결과를 얻을 수 있다.
동적 서비스 호출은 기본적인 웹 서비스의 호출 과정을 그대로 구현한 것으로 이해하면 된다. 즉 서비스 사용자가 UDDI 서버에서 원하는 서비스를 동적으로 찾아 WSDL을 통해 서비스를 호출하는 과정을 볼 때 미리 스텁을 가지고 서비스를 호출하는 것은 기대하기 힘들 것이다. 미리 WSDL을 xrpcc 컴파일러로 컴파일할 수 없기 때문인데 이러한 형태의 서비스 호출 또한 빈번히 일어날 수 있으므로, 이에 대한 인터페이스를 반드시 가지고 있어야 한다.
예를 들어, 사용자가 특정 영화를 예매하려고 한때 이 영화의 예매를 처리하는 사이트가 많을 수 있으므로 사용자는 여러 예매 지원 사이트 인터페이스 중 하나를 골라서 예매를 하려고 할 것이다. 이때 사용자의 선택은 동적이기 때문에 어떤 인터페이스로 구현될 지 알 수 없다. 따라서 클라이언트 프로그램은 이러한 경우를 위해 WSDL만 보고 적절한 형태의 서비스를 호출해야 한다.
4. 동적 호출 인터페이스 모델
4.1 개념
동적 호출 인터페이스 모델이란 클라이언트 측에서 원격 인터페이스명을 알지 못해도 동적으로 원격 프로시저를 호출할 수 있도록 해 주는 모델이다.
앞서 살펴본 두 가지의 호출방식에서는 클라이언트 응용 프로그램이 원격 프로시저를 호출하기 위해서는 생성된 스텁을 원격 인터페이스의 형으로 변환해야 한다. 따라서 클라이언트 응용프로그램 작성할때 원격 인터페이스가 반드시 있어야 한다.
정적 스텁 호출 모델에서는 스텁 클래스를 생성할 때 원격 인터페이스도 자동으로 만들어진다. 동적 프록시 호출 모델에서는 스텁이 실행 도중에 자동으로 생성되지만, 원격 인터페이스는 웹 서비스 제작자로 부터 얻거나 또는 wscompile.bat을 통해서 생성해야 한다.
하지만 동적 호출 인터페이스는 원격 인터페이스로 형 변환 없이 Call 객체를 통해서 바로 원격 프로시저를 호출할 수 있다. 즉 웹 서비스 소비자인 클라이언트에서는 원격 인터페이스가 없어도 단지 원격 프로시저명과 인자에 대한 정보만 알고 있으면 원격 프로시저를 호출할 수 있다.
다음 그림은 동적 호출 인터페이스의 호출 과정을 나타내고 있다.
4.2 클라이언트 프로그램 작성
Hello 웹 서비스 시스템을 호출하기 위한 동적 호출 인터페이스 방식의 클라이언트 프로그램은 다음과 같이 작성할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
HelloClient.java - 동적 호출 인터페이스 방식의 클라이언트 프로그램 package hello; import javax.xml.rpc.*; import javax.xml.namespace.*; import java.net.*; public class HelloClient { public static void main(String[] args) { try { ServiceFactory serviceFactory = ServiceFactory.newInstance(); QName serviceQName = new QName( "http://localhost:8080/hello/webservice/wsdl/webservice", "Webservice"); Service service = serviceFactory.createService(serviceQName); QName portQName = new QName( "http://localhost:8080/hello/webservice/wsdl/webservice", "HelloIFPort"); Call call = service.createCall(portQName); //웹 서비스 종점 지정 call.setTargetEndpointAddress("http://localhost:8080/hello/webservice"); //SOAP Action HTTP 헤더 변수 설정 call.setProperty(Call.SOAPACTION_USE_PROPERTY, new Boolean(true)); call.setProperty(Call.SOAPACTION_URI_PROPERTY, ""); //SOAP 메시지 인코딩 지정 call.setProperty("javax.xml.rpc.encodingstyle.namespace.uri", "http://schemas.xmlsoap.org/soap/encoding/"); //“string” XML 스키마 형을 갖는 변수 설정 call.setReturnType( new QName("http://www.w3.org/2001/XMLSchema", "string")); //호출할 메소드 명시 call.setOperationName( new QName("http://localhost:8080/hello/webservice/wsdl/webservice", "sayHello")); //호출할 메소드의 인자형을 지정 call.addParameter("String_1", new QName("http://www.w3.org/2001/XMLSchema", "string"), ParameterMode.IN); //서비스 호출 Object params[] = { new String("Everybody") }; String result = (String) call.invoke(params); System.out.println(result); } catch (Exception ex) { ex.printStackTrace(); } } } |
10-12줄
ServiceFactory 및 Service 객체 생성
동적 프록시 호출 모델에서의 생성과 동일하다.
13-15줄
Call 객체 생성
동적 호출 인터페이스 방식에서 클라이언트는 웹 서비스 시스템과 통신할 스텁을 만드는 대신에 javax.xml.rpc 패키지의 Call 인터페이스 형 객체를 만들어야 한다. Call 객체의 생성을 위해서는 Service 객체의 createCall 메소드를 다음과 같이 사용하면 된다.
Call call = service.createCall (포트QName객체); 포트 QName 객체를 생성하는 방법에 대한 설명은 동적 프록시 호출 모델에서 스텁 객체 생성 부분의 설명을 참조하기 바란다. |
16-34줄
Call 객체의 속성값 지정
Call 객체가 특정 웹 서비스 시스템의 통신용 스텁 객체를 사용하기 위해서는 적절한 속성값을 설정해 주어야 한다.
구분 | 관련 메소드 | 설명 |
종점 지정 | call.setTargetEndPointAddress("종점URL"); | |
SOAPACTION 사용 여부 |
call.setProperty( Call.SOAPACTION_USE_PROPERTY, new Boolean(true) or new Boolean(false)); |
false : (디폴트) 사용 안 함 true : 사용 (일반적) |
SOAPACTION URI 지정 |
call.setProperty("URI"); | URI 경로가 없는 경우에는 빈 문자열로 지정 |
SOAP 메시지 인코딩 지정 |
call.setProperty( "javax.xml.rpc.encodingstyle.namespace.uri", "http://schemas.xmlsoap.org/soap/encoding/"); |
기본 인코딩을 SOAP 인코딩으로 지정 |
원격 프로시저 이름 지정 |
call.setOperationName( new QName( "원격 프로시저 네임스페이스명", "원격 프로시저명") ); |
원격 프로시저명은 WSDL 문서에 있는 <operation> 엘리먼트의 name 속성값을 참조 |
원격 프로시저 인자형 지정 |
call.addParameter("인자명", new QName( "http://www.w3.org/2001/XMLSchema", "인자형"), ParameterMode.IN ); |
addParameter는 인자 개수만큼 존재해야 함. 인자명은 WSDL에 있는 <part> 엘리먼트의 name 속성값을 참조함. 인자형은 XML 스키마의 빌트인 SimpleType이 사용되어야 함. |
원격 프로시저 반환형 지정 |
call.setReturnType( new QName( "http://www.w3.org/2001/XMLSchema", "반환형"), ); |
35-37줄
Call 객체를 사용한 원격 메소드 호출
Call 객체를 통해 원격 메소드를 호출하기 위해서는, 우선 원격 프로시저의 인자 정보를 java.lang.Object 배열로 작성하여 Call 객체의 invoke() 메소드의 인자로 전달한다.
Object 참조변수[] = {인자값1, 인자값2, ..., 인자값n}; 반환형 변환참조변수 = call.invoke(참조변수) |
5. SAAJ 기반의 클라이언트
우리는 SOAP 메시지를 생성하고 전송하기 위한 방법으로서 SAAJ API를 이용하는 방법에 대해서 이미 살펴보았다. 이러한 SAAJ API를 이용해서 웹 서비스 클라이언트 응용 프로그램을 작성할 수 있으며, 다음은 Hello 웹 서비스에 대한 클라이언트 프로그램의 예제이다.
HelloClient.java - SAAJ API 기반의 웹 서비스 클라이언트 프로그램 import java.io.*; import java.util.*; import javax.xml.parsers.*; import org.w3c.dom.*; import org.xml.sax.*; import javax.xml.soap.*; import javax.xml.transform.dom.*; public class HelloClient { /// Field /// Constructor /// Method public static void main(String args[]) { try { //DOM 파서 생성 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder parser = factory.newDocumentBuilder(); //요청 SOAP 메시지 DOMSource 생성 String sendMessage = "<?xml version=₩"1.0₩" encoding=₩"UTF-8₩"?>" + "<env:Envelope " + " xmlns:env=₩"http://schemas.xmlsoap.org/soap/envelope/₩"" + " xmlns:xsd=₩"http://www.w3.org/2001/XMLSchema₩"" + " xmlns:xsi=₩"http://www.w3.org/2001/XMLSchema-instance₩"" + " xmlns:enc=₩"http://schemas.xmlsoap.org/soap/encoding/₩"" + " xmlns:ns0=₩"http://localhost:8080/hello/webservice/wsdl/webservice₩"" + " env:encodingStyle=₩"http://schemas.xmlsoap.org/soap/encoding/₩">" + " <env:Body>" + " <ns0:sayHello>" + " <String_1 xsi:type=₩"xsd:string₩">Everybody</String_1>" + " </ns0:sayHello>" + " </env:Body>" + "</env:Envelope>"; StringReader reader = new StringReader(sendMessage); InputSource is = new InputSource(reader); Document document = parser.parse(is); DOMSource requestSource = new DOMSource(document); //SOAPMessage 객체 생성 MessageFactory messageFactory = MessageFactory.newInstance(); SOAPMessage requestSoapMessage = messageFactory.createMessage(); SOAPPart requestSoapPart = requestSoapMessage.getSOAPPart(); requestSoapPart.setContent(requestSource); //SOAPConnection 객체 생성 SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance(); SOAPConnection connection = scf.createConnection(); //요청 SOAP 메시지 보내고, 응답 SOAP 메시지 받기 SOAPMessage responseSoapMessage = connection.call(requestSoapMessage, "http://localhost:8080/hello/webservice"); //응답 결과 얻기 SOAPPart responseSoapPart = responseSoapMessage.getSOAPPart(); SOAPEnvelope se = responseSoapPart.getEnvelope(); SOAPBody sb = se.getBody(); Name name = se.createName("sayHelloResponse","ns0", "http://localhost:8080/hello/webservice/wsdl/webservice"); Iterator iterator = sb.getChildElements(name); SOAPElement eSayHelloRespons = (SOAPElement) iterator.next(); name = se.createName("result"); iterator = eSayHelloRespons.getChildElements(name); SOAPElement eResult = (SOAPElement) iterator.next(); iterator = eResult.getChildElements(); javax.xml.soap.Text text = (javax.xml.soap.Text) iterator.next(); System.out.println(text.getValue()); } catch(Exception e) { e.printStackTrace(); } } } |
정리하기
① JAX-RPC가 제공하는 웹 서비스 호출 방식에는 정적 서비스 호출 방식과 동적 서비스 호출 방식이 있다.
② 정적 스텁 호출 방식은 수동으로 생성된 스텁을 이용해서 원격 메소드를 호출하는 방식으로서, WSDL 문서가 변경되면 코드가 변경되고 클라이언트 프로그램은 다시 컴파일되어야 한다.
③ 동적 프록시 호출 방식에서도 WSDL 문서가 필요하지만 정적 모델처럼 수동으로 스텁 객체를 생성 하지 않고 실행 도중에 스텁 객체가 생성되는 특징을 가지고 있기 때문에 인터페이스만 알고 있으면 이를 구현한 서비스의 소스 코드를 다시 컴파일하지 않아도 동적으로 런타임시 호출할 수 있다.
④ 동적 호출 인터페이스 방식에서는 원격 인터페이스로 형 변환 없이 Call 객체를 통해서 바로 원격 프로시저를 호출할 수 있기 때문에 웹 서비스 소비자인 클라이언트에서는 원격 인터페이스가 없어도 단지 원격 프로시저명과 인자에 대한 정보만 알고 있으면 원격 프로시저를 호출할 수 있다.
⑤ 각 호출 방식에 따른 클라이언트 프로그램의 작성 방법에 대해서는 각자 정리를 부탁한다.
⑥ JAX-RPC API를 사용하는 방법에 대한 자세한 사항은 다음 사이트를 참조하기 바란다.
* http://java.sun.com/webservices/
* http://java.sun.com/webservices/docs/1.1/tutorial/doc/
* http://java.sun.com/webservices/docs/1.2/tutorial/doc/
* http://java.sun.com/webservices/docs/1.3/tutorial/doc/
'정보과학 > 웹서비스특론' 카테고리의 다른 글
웹 서비스 공개 및 검색, UDDI (0) | 2023.09.13 |
---|---|
웹 서비스 명세화, WSDL (0) | 2023.09.12 |
웹 서비스 시스템 개발 (1) | 2023.09.09 |
SOAP API (0) | 2023.09.08 |
SOAP (0) | 2023.09.07 |