웹 서비스의 기본 중 하나는 상호운용성이다. 웹 서비스는 표준 형식으로 메시지를 주고 받는다. 일반적으로 이 형식은 SOAP이 대표적인 것이라고 볼 수 있는데 SOAP 메시지를 보내는 방식은 다양하다.
가장 기본적인 것은 URL 스트림에 println을 수행하는 것이지만 이렇게 하려면
하나의 서비스가 보내려고 하는 SOAP 프로토콜과 SOAP 서비스에 대해 지나치게 많은 것을 알아야 한다. 그 서비스를
통해 이러한 정보를 얻기란 쉬운 일이 아니고 이러한 방식은 확장성이 없다.
좀 더 나은 방법으로는 SAAJ API (SOAP with Attachments API for Java)를
사용하는 것이다. (지난
글 참조). SAAJ API는 SOAP 구조체들을 보다 추상적인 단계에서 조작하는 방법을 제공해주지만
URL 스트림에 대해서는 println에 버금가는 문제들을 안고있다.
훨씬 친근한 접근방식으로는 애플리케이션을 SOAP 메시징 프로토콜 위에 있는 추상 레벨로 가져오는 시스템을 사용하는 것이다. 그 시스템의 이점은 다음과 같다:
- 애플리케이션 프로그래머들이 까다로운 SOAP 보다는 애플리케이션 로직에 집중할 수 있다.
- SOAP 외에 메시징 스키마들이 사용될 수 있고 그 애플리케이션 코드는 매우 적게 변경된다.
Java APIs for XML-based RPC (JAX-RPC)는 그와 같은 추상 접근방식을 제공한다.
SOAP에 의존하는 대신 JAX-RPC는 Web Services Description Language (WSDL)에 의존한다. WSDL은 웹 서비스에 대한 API를 서술적이며 표준화된 방식으로 정의한다. WSDL은 서비스용 SOAP 바인딩을 정의하지만 SOAP 메시징 레이어 위에 있는 레벨에서 서비스의 추상적 디스크립션 또한 정의한다. (WSDL)
위에 언급된 SAAJ 예제는 www.xmethods.net의 Barnes & Noble 웹 서비스를 사용했다. XMethods 사이트는 이 웹 서비스용 WSDL을 제공한다. (참고자료). 그 WSDL이 다음 예제에 사용되고 있다.(Listing 1).
Listing 1. XMethods의 Barnes & Noble WSDL
<?xml version="1.0" ?>
<definitions name="BNQuoteService"
targetNamespace="http://www.xmethods.net/sd/BNQuoteService.wsdl"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://www.xmethods.net/sd/BNQuoteService.wsdl">
<message name="getPriceRequest">
<part name="isbn" type="xsd:string" />
</message>
<message name="getPriceResponse">
<part name="return" type="xsd:float" />
</message>
<portType name="BNQuotePortType">
<operation name="getPrice">
<input message="tns:getPriceRequest" name="getPrice" />
<output message="tns:getPriceResponse" name="getPriceResponse" />
</operation>
</portType>
<binding name="BNQuoteBinding" type="tns:BNQuotePortType">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http" />
<operation name="getPrice">
<soap:operation soapAction="" />
<input name="getPrice">
<soap:body
use="encoded"
namespace="urn:xmethods-BNPriceCheck"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</input>
<output name="getPriceResponse">
<soap:body
use="encoded"
namespace="urn:xmethods-BNPriceCheck"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</output>
</operation>
</binding>
<service name="BNQuoteService">
<documentation>Returns price of a book at BN.com given an
ISBN number</documentation>
<port name="BNQuotePort" binding="tns:BNQuoteBinding">
<soap:address location=
"http://services.xmethods.net:80/soap/servlet/rpcrouter" />
</port>
</service>
</definitions> |
JAX-RPC 애플리케이션에서 다음 세 가지를 수행해야 한다:
-
Service클래스를 인스턴스화 한다. 이것은 웹 서비스의 클라이언트 쪽 표현이 된다. - 웹 서비스 애플리케이션에 대한 프록시를 인스턴스화 한다. (그 프록시를 설정할 수도 있다.)
- 웹 서비스 애플리케이션 실행을 호출한다.
SAAJ API가 WSDL(SOAP)에 의존하는 것은 아니지만 서비스용 SOAP 메시지가 어떻게 보여지는가에 대해 정확히 알아야 한다. JAX-RPC는 Dynamic Invocation Interface (DII) API를 정의하여 WSDL에 의존하지 않는 웹 서비스에 접근한다. 하지만 SOAP 메시지의 세부적인 사항 까지 알아야 할 필요는 없다. DII JAX-RPC 애플리케이션 고유의 세 단계는 다음과 같다:
- WSDL 없이 DII
Service클래스를 인스턴스화한다. - DII
Call객체 프록시를 인스턴스화 하고 이를 설정한다. -
Call객체의invoke메소드를 호출한다.
WSDL 없이 DII Service 클래스를 인스턴스화한다
이 클래스는 WSDL을 인식하지 않는다. 하지만 이름은 갖고 있어야한다. WSDL에서 서비스 이름을 사용한다.
Listing 2. DII
Service 클래스의 인스턴스화import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class DIITip {
public static void main(String args[]) {
try {
// Create a service class with no WSDL information. You
// still must know something about the WSDL, however: the
// service's name.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(serviceName);
}
catch (Throwable t) {
t.printStackTrace();
}
}
} |
DII 프록시 객체는 javax.xml.rpc.Call 인터페이스를 구현한다. 여러분이 만든 Call 인터페이스의 구현을 갖게된다.
Listing 3.
Call 객체의 인스턴스
생성import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class DIITip {
public static void main(String args[]) {
try {
// Create a service class with no WSDL information. You
// still must know something about the WSDL, however: the
// service's name.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(serviceName);
// Now create a dynamic Call object from this service.
// This call object is not yet associated with any
// operation. We'll do that below.
Call call = service.createCall();
}
catch (Throwable t) {
t.printStackTrace();
}
}
} |
지금 갖고있는 Call 객체는 아직 사용할 수 없다. 작동에 대해 아무것도 모르고
있다. 그 객체에 다음과 같은 데이터를 제공하라:
- 작동 이름:
getPrice - 인풋 매개변수: string
- 리턴 유형: float
- 정보 바인딩: rpc style; encoding style
- 엔드포인트:
http://services.xmethods.net:80/soap/servlet/rpcrouter
Listing 4.
Call 객체의 파퓰레이팅import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import javax.xml.rpc.encoding.XMLType;
public class DIITip {
public static void main(String args[]) {
try {
// Create a service class with no WSDL information. You
// still must know something about the WSDL, however: the
// service's name.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(serviceName);
// Now create a dynamic Call object from this service.
// This call object is not yet associated with any
// operation. We'll do that below.
Call call = service.createCall();
// Next, build up the information about the operation...
// The operation name
QName operationName = new QName(
"urn:xmethods-BNPriceCheck",
"getPrice");
call.setOperationName(operationName);
// The input parameter
call.addParameter(
"isbn", // parameter name
XMLType.XSD_STRING, // parameter XML type QName
String.class, // parameter Java type class
ParameterMode.IN); // parameter mode
// The return
call.setReturnType(XMLType.XSD_FLOAT);
// The operation is an RPC-style operation.
call.setProperty(
Call.OPERATION_STYLE_PROPERTY,
"rpc");
// The encoding style property value comes from the
// binding's operation's input clauses encodingStyle
// attribute. Note that, in this case, this call is not
// really necessary - the value we're setting this
// property to is the default.
call.setProperty(
Call.ENCODINGSTYLE_URI_PROPERTY,
"http://schemas.xmlsoap.org/soap/encoding/");
// The target endpoint
call.setTargetEndpointAddress(
"http://services.xmethods.net:80/soap/servlet/rpcrouter");
}
catch (Throwable t) {
t.printStackTrace();
}
}
} |
getPrice 작동을 호출 할 준비가 되었다.
Listing 5.
getPrice 작동 호출하기import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import javax.xml.rpc.encoding.XMLType;
public class DIITip {
public static void main(String args[]) {
try {
// Create a service class with no WSDL information. You
// still must know something about the WSDL, however: the
// service's name.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(serviceName);
// Now create a dynamic Call object from this service.
// This call object is not yet associated with any
// operation. We'll do that below.
Call call = service.createCall();
// Next, build up the information about the operation...
// The operation name
QName operationName = new QName(
"urn:xmethods-BNPriceCheck",
"getPrice");
call.setOperationName(operationName);
// The input parameter
call.addParameter(
"isbn", // parameter name
XMLType.XSD_STRING, // parameter XML type QName
String.class, // parameter Java type class
ParameterMode.IN); // parameter mode
// The return
call.setReturnType(XMLType.XSD_FLOAT);
// The operation is an RPC-style operation.
call.setProperty(
Call.OPERATION_STYLE_PROPERTY,
"rpc");
// The encoding style property value comes from the
// binding's operation's input clauses encodingStyle
// attribute. Note that, in this case, this call is not
// really necessary - the value we're setting this
// property to is the default.
call.setProperty(
Call.ENCODINGSTYLE_URI_PROPERTY,
"http://schemas.xmlsoap.org/soap/encoding/");
// The target endpoint
call.setTargetEndpointAddress(
"http://services.xmethods.net:80/soap/servlet/rpcrouter");
// Invoke the operation
Object[] actualArgs = {"0672324229"};
Float price = (Float) call.invoke(actualArgs);
System.out.println("price = " + price);
}
catch (Throwable t) {
t.printStackTrace();
}
}
} |
java DIITip을 실행하면 다음과 같은 값을 얻는다: 44.99.
이것을 SAAJ 아티클의 코드와 비교해보면 SOAP 프로토콜에 대한 지식에서 상당히 멀어졌음을 알 수 있다. 여기서 필요한 유일한 SOAP 관련 정보는 작동 스타일과 인코딩 스타일이다.
DII 프로그래밍 모델은, 그 이름이 시사하는 바와 같이 동적 서비스로 작동하도록 설계되었다. 서비스가 약간 변경되면 매치하기 위해 클라이언트를 많이 변경하지 않는다. 조금만 영리하다면 클라이언트 코드는 전혀 변경할 필요가 없도록 완전히 동적인 것이 될 수도 있다.
이 모델은 여전히 복잡하다. 특히 Call 객체를 파퓰레이팅 해야하는 장소에서
그렇다. 작동이 복잡할 수록 Call 객체에 제공해야하는 정보도 복잡해진다. 따라서
서비스가 정적이고 변하지 않는다는 것을 안다면 추상 먹이 사슬로 한단계 더 올라갈 수 있다.
다음 단계의 추상화에는 설정이 필요하다. 하지만 결과 클라이언트 코드는 훨씬 더 단순하다. WSDL로 시작한다. JAX-RPC 구현은 Service Endpoint Interface (SEI)를 만드는 WSDL-자바 코드 생성기를 제공한다. 이것은 웹 서비스 애플리케이션에 대한 클라이언트쪽 자바 인터페이스이다. Listing 6은 XMethods Barnes & Noble 애플리케이션용 SEI이다.
Listing 6. Barnes & Noble SEI
package xmethods.bn;
public interface BNQuotePortType extends java.rmi.Remote {
public float getPrice(java.lang.String isbn)
throws java.rmi.RemoteException;
} |
내가 사용했던 WSDL-자바 툴에서 http://www.xmethods.net/sd/BNQuoteService.wsdl의
targetNamespace를 자바 패키지 xmethods.bn으로
매핑했다. 따라서 이것은 SEI가 만들어졌던 패키지이다. SEI의 이름은 WSDL의 portType(BNQuotePortType)에
에서 온 것이다. getPrice 메소드는 portType의
getPrice 작동과 이것이 참조하는 메시지와 유형에서 파생되었다. 각자 선호하는
WSDL-자바 툴을 실행하면 xmethods.bn 패키지에 생성된 클래스가 있다는
것을 알게된다. 그들 모두를 컴파일 해야하지만 클라이언트 애플리케이션은 오직 SEI에 대해서만 인식하면
된다.
SEI를 사용하여 웹 서비스를 호출하려면 DII에서 했던 것과 같은 세 단계를 수행해야 한다:
- WSDL로
Service클래스의 인스턴스화 - 프록시 인스턴스화
- 프록시 작동 호출
SEI와 DII 모두 1 단계는 비슷하다. 2-3 단계는 더 단순하다. 특히 2 단계가 그렇다.
SEI 모델과 DII 모델에서 이 단계의 유일한 차이점은 여기에서는 WSDL에 대한 약간의 추가정보를 제공한다는 점이다.
Listing 7. SEI 용
Service
호출 인스턴스화
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class SEITip {
public static void main(String args[]) {
try {
// Create a service class with WSDL information.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
URL wsdlLocation = new URL
("http://www.xmethods.net/sd/2001/BNQuoteService.wsdl");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(
wsdlLocation,
serviceName);
}
catch (Throwable t) {
t.printStackTrace();
}
}
} |
일단 서비스를 받으면 SEI 구현을 찾아야한다. 이 구현은 실제 애플리케이션에 대한 프록시이다. 이 애플리케이션에 액세스 하는 방법과 관련한 모든 정보는 구현 안에 숨겨져있다. 이것은 WSDL의 서비스 포트에서 모아진다. 따라서 일단 프록시를 얻게되면 어떤 설정도 수행할 필요가 없다. 설정이 되어있기 때문이다.
Listing 8. SEI용 구현의 인스턴스화
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import xmethods.bn.BNQuotePortType;
public class SEITip {
public static void main(String args[]) {
try {
// Create a service class with WSDL information.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
URL wsdlLocation = new URL
("http://www.xmethods.net/sd/2001/BNQuoteService.wsdl");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(
wsdlLocation,
serviceName);
// Get an implementation for the SEI for the given port
QName portName = new QName("", "BNQuotePort");
BNQuotePortType quote = (BNQuotePortType) service.getPort(
portName,
BNQuotePortType.class);
}
catch (Throwable t) {
t.printStackTrace();
}
}
} |
마지막으로 작동 호출은 이보다 더 간단할 수 없다:
Listing 9. SEI에서 작동 호출하기
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import xmethods.bn.BNQuotePortType;
public class SEITip {
public static void main(String args[]) {
try {
// Create a service class with WSDL information.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
URL wsdlLocation = new URL
("http://www.xmethods.net/sd/2001/BNQuoteService.wsdl");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(
wsdlLocation,
serviceName);
// Get an implementation for the SEI for the given port
QName portName = new QName("", "BNQuotePort");
BNQuotePortType quote = (BNQuotePortType) service.getPort(
portName,
BNQuotePortType.class);
// Invoke the operation
float price = quote.getPrice("0672324229");
System.out.println("price = " + price);
}
catch (Throwable t) {
t.printStackTrace();
}
}
} |
- developerWorks worldwide 사이트에서 이 기사에 관한 영어원문.
-
소스코드
다운로드.
-
Web
Services Description Language (WSDL) 1.1.
-
WSDL
튜토리얼.
-
Barnes
& Noble Web service
다운로드.
-
IBM
WebSphere SDK for Web Services (WSDK).
-
Java
API for XML-Based RPC (JAX-RPC) Downloads & Specifications.
-
AXIS.
-
developerWorks
XML
존.
- developerWorks 웹 서비스 존.