제22장 JAX-RPC 웹 서비스 호출

내용 목차

22.1. JAX-RPC 웹 서비스 호출(JavaSE 클라이언트)
22.1.1. Stub 클라이언트
22.1.2. DII 클라이언트
22.2. JAX-RPC 웹 서비스의 호출(JavaEE 클라이언트)
22.2.1. JavaEE 클라이언트 프로그래밍 모델
22.2.2. JavaEE 클라이언트 프로그래밍 절차
22.2.3. JavaEE 클라이언트의 작성과 예제

본 장에서는 JAX-RPC 웹 서비스를 호출하기 위한 여러 가지 클라이언트 작성과 호출 방법에 대해 설명한다.

22.1. JAX-RPC 웹 서비스 호출(JavaSE 클라이언트)

‘웹 서비스 호출’은 웹 서비스를 사용하기 위한 클라이언트 응용 프로그램이 수행하는 동작을 의미한다. 클라이언트 응용 프로그램은 Java, 또는 .NET과 같은 여러 다양한 기술을 이용하여 JEUS 에 배치된 웹 서비스를 호출할 수 있다.

웹 서비스 클라이언트는 웹 서비스에게 서비스를 요청하는 프로그램이다.

JEUS JAX-RPC 웹 서비스를 사용하여 2가지 타입의 웹 서비스 클라이언트를 생성할 수 있다.

  • Stub Client

    이 타입의 웹 서비스 클라이언트는 웹 서비스의 WSDL로부터 생성된 stub을 이용한다.

  • DII (Dynamic Invocation Interface) 클라이언트

    이 타입의 웹 서비스 클라이언트는 JAX-RPC 클라이언트 API를 이용한다.

22.1.1. Stub 클라이언트

Stub 클라이언트는 특정 웹 서비스의 WSDL로부터 생성된 로컬 Stub상의 메소드를 호출한다. Stub 객체는 원격의 웹 서비스와 상호작용을 담당한다.

본 절에서는 “제21장 JAX-RPC 웹 서비스의 생성과 배치”에서 이미 작성했던 ‘DocLitEchoService’ 웹 서비스의 메소드를 호출하는 웹 서비스 클라이언트 프로그램을 작성하는 방법을 설명한다.

22.1.1.1. WSDL로부터 웹 서비스 Stub 생성

웹 서비스 Stub 소스 코드를 생성하기 위해서 wsdl2java Ant Task 나 wsdl2java 명렁어를 사용한다. Ant Task를 사용하는 경우 build.xml의 코드는 다음과 같다.

[예 22.1] << build.xml >>

...
<target name="-pre-compile">
    <antcall target="wsdl2java">
        <param name="wsdl2java.option" value="-gen:client -d ${build.classes.dir}
-package echo -compile -verbose ${src.conf}/DocLitEchoService.wsdl" />
    </antcall>
</target>
...


wsdl2java Task에 대한 더 자세한 설명은 JEUS Reference Book”의 “4.11. wsdl2java”를 참고한다.

다음과 같은 명령어를 사용하여 웹 서비스 Stub 소스 코드를 생성할 수 있다.

C:\wsclient>ant -pre-compile

만약 위의 과정이 성공했다면 웹 서비스 Stub 소스 코드가 생성된다.

C:\wsclient\build\classes\echo\
                              |-- DocLitEchoService.java
                              |-- DocLitEchoService_Impl.java
                              |-- Echo.java 
                              +-- ... </screen>

Stub 소스 코드는 wsdl2java Task의 destDir 속성으로 지정한 디렉터리 아래에 컴파일된다.

생성된 Java 파일 및 Java 파일 안의 메소드의 이름은 웹 서비스의 WSDL로부터 매핑된다. WSDL 에 대응하는 Java 이름 매핑은 [표 22.1]에서 볼 수 있다.

[표 22.1] WSDL요소와 Java 구성 매핑

WSDL element매핑
<service>

서비스 인터페이스와 구현 Java 클래스에 매핑된다.

<service> element의 name 속성값이 인터페이스의 이름이다. 구현 파일은 <service> element의 이름 뒤에 '_Impl'이 붙는다.

<port>

<service> element의 서비스 인터페이스와 구현 클래스안의 메소드에 매핑된다.

메소드의 이름은 “get” + “<port> element의 name 속성값”으로 이루어진다.

<portType>

웹 서비스 operation들을 위한 Java 인터페이스와 구현 Java 클래스에 매핑된다.

이 Java 파일 이름은 <portType> element의 name 속성을 가지고 만들어진다. 구현 클래스의 Java 파일명은 <portType> element의 name 속성값에 '_Stub'이 붙은 형태이다.


예제에서는 다음과 같은 소스 파일들이 생성된다.

클래스설명
Echo.javaportType 인터페이스 클래스
Echo_Stub.javaportType 인터페이스 클래스의 Stub 클래스
DocLitEchoService.java서비스 인터페이스 클래스
DocLitEchoService_Impl.java서비스 구현 클래스
*.java기타 생성 클래스

22.1.1.2. 웹 서비스 클라이언트 작성

본 절에서는 웹 서비스 클라이언트의 작성을 위해서 위에서 생성한 4개의 클래스들을 어떻게 이용할 것인가를 알아본다.

다음은 웹 서비스 클라이언트를 작성하는 방법이다.

  1. 웹 서비스 객체 생성

    처음 단계는 원격 웹 서비스를 위해 서비스 구현 객체를 생성하는 것이다. 예제에서 서비스 구현 클래스는 ‘DocLitEchoService_Impl’이다. 다음은 웹 서비스 객체 생성을 위한 Java 코드이다.

    DocLitEchoService service = new DocLitEchoService_Impl();
  2. Stub 객체로부터 Port 객체 얻기

    일단 서비스 구현 객체가 생성되었다면 두 번째 단계로 서비스 구현 객체로부터 Port 객체를 얻는다.

    서비스 인터페이스 클래스는 Port 객체를 얻기 위한 메소드들을 제공한다. 메소드 이름은 “get” + “WSDL의 <port> element의 name 속성값”으로 구성되며, WSDL의 <portType> element의 name 속성을 이름으로 가지는 타입이 리턴된다.

    WSDL Port 이름은 웹 서비스의 WSDL 문서의 <port> element 안에 기술되어 있다. <port> element는 <service> element의 하위 element이다. 이 예제에서의 WSDL문서는 다음과 같다.

    <wsdl:portType name="Echo">
        . . .
    </wsdl:portType>
    . . .
    <wsdl:service name=”DocLitEchoService”>
        <wsdl:port binding=”impl:EchoSoapBinding” name=”EchoPort”>
            <soap:address location=
    "http://localhost:8088/DocLitEchoService/DocLitEchoService"/>
        </wsdl:port>
    </wsdl:service>

    서비스 인터페이스 클래스인 DocLitEchoService.java는 다음과 같다(여기서는 발췌한 내용만 보여준다).

    [예 22.2] << DocLitEchoService.java>>

    package echo;
    
    public interface DocLitEchoService extends javax.xml.rpc.Service {
        public java.lang.String getEchoPortAddress();
    
        public echo.Echo getEchoPort() 
            throws javax.xml.rpc.ServiceException;
    }


    다음은 서비스 객체로부터 Port 객체를 얻기 위한 소스 코드이다.

    DocLitEchoService service = new DocLitEchoService_Impl();
    Echo port = service.getEchoPort();
  3. Port 객체상의 Operation 실행

    현재 우리는 원격 웹 서비스에서 제공하는 operation들을 위한 Port 객체를 가지고 있다. Port 객체의 메소드들을 호출함으로써 웹 서비스 operation을 실행할 수 있다. 예제에서 웹 서비스의 Port는 'echoString'이라는 operation을 제공한다. Operation을 실행하기 위한 코드는 다음과 같다.

    DocLitEchoService service = new DocLitEchoService_Impl();
    Echo port = service.getEchoPort();
    String s = port.echoString(“JEUS”);

    또는 위의 두 라인을 하나의 라인으로 만들 수 있다.

    Echo port = new DocLitEchoService_Impl().getEchoPort();
    String s = port.echoString(“JEUS”);

완전한 예제

다음은 웹 서비스 클라이언트의 완전한 예제이다.

[예 22.3] <<ProxyClient.java>>

package j2se;

import echo.DocLitEchoService_Impl;
import echo.Echo;

public class ProxyClient {
    public static void main(String args[]) {
        ProxyClient client = new ProxyClient();
        client.run();
    }

    public void run() {
        try {
            Echo port = new DocLitEchoService_Impl().getEchoPort();
            String s = port.echoString("JEUS");
            System.out.println("response = " + s);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}


이 파일은 C:\wsclient\src\j2se 디렉터리에 있다고 가정한다.

22.1.1.3. 웹 서비스 클라이언트 컴파일

ProxyClient 코드의 컴파일을 위해서 wsclient 디렉터리에서 다음의 명령어를 콘솔에 적용한다.

C:\jeus\samples\webservice\jaxrpc\from_java\doclit\doclit-client>ant build

컴파일이 성공했다면 컴파일된 클래스는 다음과 같을 것이다.

C:\jeus\samples\webservice\jaxrpc\from_java\doclit\doclit-client\build\classes\j2s
e\ProxyClient.class

22.1.1.4. 웹 서비스 클라이언트 실행

‘JEUS_HOME\samples\webservice\jaxrpc\from_java\doclit\doclit-client’ 디렉터리에서 다음의 명령어를 적용한다.

C:\jeus\samples\webservice\jaxrpc\from_java\doclit\doclit-client>ant run

실행결과는 다음과 같을 것이다.

Response = JEUS

22.1.2. DII 클라이언트

DII(Dynamic Invocation Interface)를 이용하면, 클라이언트는 실행되기 전까지 Remote Operation의 signature나 웹 서비스의 이름을 몰라도, Remote Operation을 호출할 수 있다.

본 절에서는 DII 클라이언트를 생성하는 방법에 대해서 설명한다.

22.1.2.1. DII 클라이언트 작성

본 절에서는 DII 클라이언트의 작성 과정에 대해 설명한다.

DII 클라이언트를 작성할 때 JAX-RPC 1.1 API를 사용한다. JEUS JAX-RPC 웹 서비스는 JAX-RPC 1.1 API를 완벽히 지원한다. 본 절에서는 JAX-RPC API의 간단한 사용법만을 보여준다.

참고

JAX-RPC에 관한 자세한 정보는 JAX-RPC 스펙(http://www.jcp.org/en/jsr/detail?id=101)과 SUN의 JAX-RPC 홈페이지(http://www.oracle.com/technetwork/java/docs-142876.html)를 참조한다.

참고로 DII 호출 방법은 RPC 방식의 웹 서비스를 호출하는 데에만 사용될 수 있다. 따라서 여기에서는 RPC 방식의 ‘RpcEncEchoService’ 서비스의 ‘echoString’ operation을 호출하는 DII 클라이언트를 작성할 것이고, 이를 위해 다음 단계를 따라 한다.

  1. 다음의 중요 문장들을 DII 클라이언트 코드에 추가해야 한다.

    import javax.xml.rpc.Call;
    import javax.xml.rpc.Service;
    import javax.xml.rpc.ServiceFactory;
    import javax.xml.rpc.ParameterMode;
    import javax.xml.namespaceQName;
  2. ServiceFactory와 Service 객체들을 생성한다.

    String targetNamespace = “urn:RpcEncService”;
    String serviceName = “RpcEncService”;
    ServiceFactory factory = ServiceFactory.newInstance();
    Service service = 
    factory.createService(new QName(targetNamespace, serviceName);
  3. Call 객체를 생성하고 설정한다.

    String operationName = “echoString”;
    QName QNAME_XSD_STRING 
    = new QName(“http://www.w3.org/2001/XMLSchema”, “string”);
    Call call = (Call) service.createCall();
    call.setTargetEndpointAddress(endpoint);
    call.setOperationName(new QName(targetNamespace, operationName));
    call.addParameter("String_1", QNAME_XSD_STRING, ParameterMode.IN);
    call.setProperty(Call.OPERATION_STYLE_PROPERTY, "rpc");
    call.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY,
                     "http://schemas.xmlsoap.org/soap/encoding/");
    call.setReturnType(QNAME_XSD_STRING);
  4. 최종적으로, Call 객체상에서 웹 서비스 operation을 실행한다.

    String ret = (String)call.invoke(new Object[] { “JEUS” } );

완전한 예제

완전한 예제는 다음과 같다.

[예 22.4] << DIIClient.java >>

package j2se;

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;

public class DIIClient {
    private static final String NS_XSD = 
        "http://www.w3.org/2001/XMLSchema";
    private static final QName QNAME_XSD_STRING = 
        new QName(NS_XSD, "string");

    public void run() {
        try {
            ServiceFactory factory = 
                ServiceFactory.newInstance();
            String endpoint = "http://localhost:8088/" +
                "RpcEncEchoService/RpcEncEchoService";
            String targetNamespace = "urn:RpcEncService";
            Service service = factory.createService(
                new QName(targetNamespace, "RpcEncService"));
            Call call = service.createCall();

            call.setTargetEndpointAddress(endpoint);
            call.setOperationName(
                new QName(targetNamespace, "echoString"));
            call.addParameter("String_1", QNAME_XSD_STRING, ParameterMode.IN);
            call.setProperty(Call.OPERATION_STYLE_PROPERTY, "rpc");
            call.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY,
                             "http://schemas.xmlsoap.org/soap/encoding/");
            call.setReturnType(QNAME_XSD_STRING);

            String ret = (String)call.invoke(
                new Object[] { "JEUS" });
            System.out.println("response = " + ret);
        } catch (Exception e) {
            System.err.println(e.toString());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        DIIClient client = new DIIClient();
        try {
            client.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


이 파일은 C:\wsclient\src 디렉터리에 있다고 가정한다.

22.1.2.2. DII 클라이언트 컴파일

DII 클라이언트 코드의 컴파일을 위해서 wsclient 디렉터리로 이동하여 다음의 명령어를 콘솔에 적용한다.

C:\jeus\samples\webservice\jaxrpc\from_java\rpcenc\rpcenc-client>ant build

컴파일이 성공했다면 컴파일된 클래스는 다음과 같다.

C:\jeus\samples\webservice\jaxrpc\from_java\rpcenc\rpcenc-client\build\classes\j2s
e\DIIClient.class

22.1.2.3. DII 클라이언트 실행

‘JEUS_HOME\samples\webservice\jaxrpc\from_java\rpcenc\rpcenc-client’ 디렉터리로 이동하여 다음의 명령어를 적용한다.

C:\jeus\samples\webservice\jaxrpc\from_java\rpcenc\rpcenc-client>ant run

실행 결과는 다음과 같을 것이다.

Response = JEUS

22.2. JAX-RPC 웹 서비스의 호출(JavaEE 클라이언트)

EJB, 서블릿과 같이 JEUS 서버에 배치된 구성 요소로부터 웹 서비스를 호출하는 것은 독립적인(stand-alone) 클라이언트로부터 호출하는것과 본질적으로는 동일하다. 그러나 현재 JavaEE 웹 서비스 스펙에서는 JavaEE 웹 서비스 클라이언트의 이식성(Portability)을 위해 프로그래밍 모델을 별도로 정의하고 있고, 이러한 모델을 따라 클라이언트를 작성하는 것을 권장한다.

22.2.1. JavaEE 클라이언트 프로그래밍 모델

웹 서비스를 클라이언트의 입장에서 본다면 클라이언트를 대신하여 비즈니스 로직을 수행하는 메소드의 집합이라 할 수 있다. 클라이언트는 메소드가 로컬에서 수행되는지, 아니면 원격에서 수행하는지를 구별할 수 없다. 클라이언트는 JAX-RPC 스펙에 정의된 SEI(Service Endpoint Interface)를 사용하여 웹 서비스에 접근한다.

JavaEE 클라이언트는 JAX-RPC에 정의된 서비스 인터페이스를 구현한 서비스 객체에 접근하기 위해서 JNDI를 사용한다. 서비스 객체는 클라이언트가 SEI를 구현한 Stub이나 프록시를 얻어오기 위해 사용하는 팩토리이다. 서비스 인터페이스는 JAX-RPC에 정의된 javax.xml.rpc.Service 인터페이스이거나, 이를 상속하여 생성된 서비스 인터페이스이다.

클라이언트 개발자는 SEI와 서비스 인터페이스를 얻어오는 것으로부터 시작하며 이들은 JAX-RPC에 정의된 WSDL →Java 매핑 법칙에 의해 생성된다. 클라이언트 개발자는 Stub을 개발 시점에 생성하지 않을 것을 권장하며, 개발 시점에는 Stub이 아닌 인터페이스를 사용할 수 있다. Stub은 클라이언트 모듈을 배치하는 시점에 클라이언트가 구동되는 환경에 맞게 자동으로 생성될 것이다. 웹 서비스의 JNDI-lookup은 논리적인 이름에 의해 이루어지며, 이 이름은 클라이언트 Deployment Descriptor에 정의된다.

22.2.2. JavaEE 클라이언트 프로그래밍 절차

JavaEE 클라이언트 프로그래밍의 기본적인 절차는 다음과 같다.

  1. JNDI Lookup을 통한 서비스(인터페이스)의 획득

  2. 서비스 인터페이스의 API를 이용한 Stub 생성 또는 Call 객체 생성

  3. Stub이나 Call 객체를 이용한 웹 서비스 호출

22.2.2.1. 서비스 Lookup

클라이언트 개발자는 서비스의 레퍼런스로 사용되는 논리적인 서비스의 JNDI 이름을 클라이언트의 Deployment Descriptor에 정의해야 한다. 필수 요구 사항은 아니지만, 모든 서비스의 논리적인 레퍼런스 이름을 JNDI Name Space의 service라는 서브 컨텍스트 아래에 구성하는 것을 권장한다.

컨테이너는 서비스 인터페이스의 구현을 클라이언트 환경 컨텍스트(java:comp/env) 하위에 서비스 레퍼런스의 논리적인 이름으로 바인딩시켜야 한다. 다음은 예제이다.

InitialContext jndiContext = new InitialContext();
Service service = (Service)jndiContext.lookup(
    "java:comp/env/service/DocLitEchoService");

위의 예제에서는 서비스 인터페이스 javax.xml.rpc.Service가 web.xml의 service-ref element의 하위 element인 service-interface element에 설정되어 있고, JNDI 이름 또한 service-ref-name element에 설정되어 서로 바인딩되게 되어 있다.

22.2.2.2. 서비스 인터페이스

서비스 인터페이스는 클라이언트가 서비스 포트에 접근하기 위해서, Stub과 동적 프록시 혹은 DII(Dynamic Invocation Interface) Call 객체를 생성하려고 할 때 사용된다. 클라이언트 개발자는 응용 프로그램이 사용하는 서비스 인터페이스의 타입을 명시적으로 클라이언트 Deployment Descriptor(web.xml)에 선언하여야 한다. 다음은 서비스 인터페이스의 타입과 JNDI 이름이 명시된 web.xml의 한 예이다.

[예 22.5] << web.xml >>

<?xml version="1.0"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee">
    <servlet>
        <servlet-name>jsp_helloClient</servlet-name>
        <jsp-file>/helloClient.jsp</jsp-file>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <service-ref>
        <service-ref-name>
            service/DocLitEchoService
        </service-ref-name>
        <service-interface>
            javax.xml.rpc.Service
        </service-interface>
        <wsdl-file>
            WEB-INF/wsdl/DocLitEchoService.wsdl
        </wsdl-file>
        <jaxrpc-mapping-file>
            WEB-INF/DocLitEchoService-mapping.xml
        </jaxrpc-mapping-file>
        <port-component-ref>
            <service-endpoint-interface>
                echo.Echo
            </service-endpoint-interface>
        </port-component-ref>
    </service-ref>
</web-app>


Stub/프록시를 이용한 호출

클라이언트는 서비스 Lookup을 통하여 가져온 서비스 인터페이스의 다음과 같은 함수들을 이용해서 Stub과 동적인 프록시를 생성할 수 있다.

java.rmi.Remote getPort(
        QName portName, Class serviceEndpointInterface)
    throws ServiceException;
java.rmi.Remote getPort(java.lang.Class serviceEndpointInterface)
    throws ServiceException;

동적인 포트 접근

클라이언트는 JNDI Lookup을 통하여 얻어온 서비스 인터페이스의 DII 메소드를 사용하여 Call 객체를 가져올 수 있다. API들은 다음과 같다.

Call createCall() throws ServiceException;
Call createCall()(QName portName) throws ServiceException;
Call createCall(QName portName, String operationName)
    throws ServiceException;
Call createCall(QName portName, QName operationName)
    throws ServiceException;
Call[] getCalls(QName portName) throws ServiceException;

ServiceFactory

JAX-RPC에서는 ServiceFactory라는 클래스에서 서비스를 생성할 수 있다. 하지만, JavaEE 응용 프로그램에서는 ServiceFactory를 사용하는 것을 권장하지 않는다. JavaEE 클라이언트는 항상 JNDI 서비스 Lookup을 통해 서비스를 가져와야 한다.

22.2.3. JavaEE 클라이언트의 작성과 예제

JavaEE 클라이언트의 작성은 WSDL을 가지고 할 수도 있고, WSDL이 없이도 가능하다. 또한, 클라이언트의 형태는 JSP, 서블릿, EJB 등 여러 형태일 수 있고, 앞에서 제시한 프로그래밍 모델에 부합하기만 하면 된다.

본 절에서는 간단한 JSP 형태의 JavaEE 클라이언트 작성 예제를 통해 작성법을 설명한다.

22.2.3.1. WSDL을 가지고 서비스 호출

WSDL을 가지고 서비스를 호출하려고 할 때에는 다음과 같은 절차를 따른다.

  1. JavaEE 클라이언트 Portable Artifact와 JAX-RPC 매핑 파일을 생성한다.

    JavaEE 클라이언트 Portable Artifact와 JAX-RPC 매핑 파일을 생성하는 것을 돕기 위해 JEUS는 wsdl2java Ant Task와 wsdl2java 커맨드 라인(Command Line) 툴을 제공한다.

    Ant Task를 사용하는 build.xml의 예는 다음과 같다.

    [예 22.6] << build.xml >>

        <target name="do-package-war">
    
            ...
    
            <antcall target="wsdl2java">
                <param name="wsdl2java.option"
                       value="-import:client
    -d ${build.war.dir}/WEB-INF/classes -package echo
    -outputmapping ${build.war.dir}/WEB-INF/DocLitEchoService-mapping.xml
    -compile ${src.web}/WEB-INF/wsdl/DocLitEchoService.wsdl" />
            </antcall>
    
            ...
    
        </target>


    wsdl2java Task에 대한 더 자세한 설명은 JEUS Reference Book”의 “5.5.2. wsdl2java”를 참조한다.

    “ant build” 명령을 수행하고 나면, JavaEE 클라이언트 Portable Artifact와 JAX-RPC 매핑 파일이 생성된다.

  2. 클라이언트 프로그램을 작성한다.

    WSDL을 가지고 클라이언트를 개발하는 경우에는 다음과 같은 java.xml.rpc.Service 인터페이스 메소드를 이용하여 Stub이나 Call 객체들을 가져올 수 있다.

    java.rmi.Remote getPort(
            QName portName, Class serviceEndpointInterface)
        throws ServiceException;
    java.rmi.Remote getPort(java.lang.Class serviceEndpointInterface)
        throws ServiceException;
    Call createCall() throws ServiceException;
    Call createCall()(QName portName) throws ServiceException;
    Call createCall(QName portName, String operationName)
        throws ServiceException;
    Call createCall(QName portName, QName operationName)
        throws ServiceException;
    Call[] getCalls(QName portName) throws ServiceException;

    다음은 위의 메소드 중 동적인 프록시(Dynamic Proxy)를 생성하여 웹 서비스를 호출하도록 JSP로 작성된 JavaEE 클라이언트의 예이다.

    [예 22.7] << helloClient.jsp >>

    <%@ page language="java" %>
    <%@ page import="javax.naming.*" %>
    <%@ page import="javax.rmi.*" %>
    <%@ page import="java.rmi.RemoteException" %>
    <%@ page import="java.util.*" %>
    
    <%@ page import="javax.naming.InitialContext" %>
    <%@ page import="javax.xml.rpc.Service" %>
    
    <%@ page import="echo.*" %>
    <%@ page errorPage="/error.html" %>
    
    <%! String msgToSend = "msg_sent_by_jspClient";
        String ret=null;
        String exceptionString="";
    %>
    <%        
            try {            
                InitialContext jndiContext = new InitialContext();
                Service service = (Service)jndiContext.lookup(
                        "java:comp/env/service/DocLitEchoService");
                java.rmi.Remote port = service.getPort(Echo.class);
                Echo echoPort = (Echo)port;
                ret = echoPort.echoString(msgToSend);
            } catch (Exception e) {
                exceptionString = e.toString();
                e.printStackTrace();
            }
    %>
    <%= "Result is "+ret+"......" 
    %>


  3. Deployment Descriptor(DD)를 작성한다.

    앞서 제시한 예제에 대한 표준 DD(web.xml)는 다음과 같이 구성되어 있다.

    [예 22.8] << web.xml >>

    <?xml version="1.0"?>
    <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee">
        <servlet>
            <servlet-name>jsp_helloClient</servlet-name>
            <jsp-file>/helloClient.jsp</jsp-file>
            <load-on-startup>0</load-on-startup>
        </servlet>
        <service-ref>
            <service-ref-name>
                service/DocLitEchoService
            </service-ref-name>
            <service-interface>
                javax.xml.rpc.Service
            </service-interface>
            <wsdl-file>
                WEB-INF/wsdl/DocLitEchoService.wsdl
            </wsdl-file>
            <jaxrpc-mapping-file>
                WEB-INF/DocLitEchoService-mapping.xml
            </jaxrpc-mapping-file>
            <port-component-ref>
                <service-endpoint-interface>
                    echo.Echo
                </service-endpoint-interface>
            </port-component-ref>
        </service-ref>
    </web-app>


    이 web.xml에는 JSP 클라이언트에서 JNDI Lookup으로 찾아오는 서비스 이름이 service-ref-name element에 설정되어 있고, 서비스 인터페이스가 javax.xml.rpc.Service 인터페이스임이 명시되어 있다.

    이 인터페이스의 구현은 wsdl-file이라는 element에 설정된 WSDL 파일의 정보를 가지고 클라이언트가 JEUS에 배치될 때 생성된다. 이때, 필요한 Java 패키지와 타입에 관한 정보는 jaxrpc-mapping-file element에 설정된 JAX-RPC 매핑 파일에서 얻는다.

    다음은 앞에 제시한 예제의 JEUS 웹 DD(jeus-web-dd.xml)이다.

    [예 22.9] << jeus-web-dd.xml >>

    <?xml version="1.0" encoding="UTF-8"?>
    <jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus">
        <service-ref>
            <service-client>
                <service-ref-name>
                    service/DocLitEchoService
                </service-ref-name>
                <port-info>
                    <wsdl-port xmlns:ns1="urn:DocLitService">
                        ns1:Echo
                    </wsdl-port>
                    <stub-property>
                        <name>
                            javax.xml.rpc.service.endpoint.address
                        </name>
                        <value>
                            http://localhost:8088/DocLitEchoService/DocLi
    tEchoService
                        </value>
                    </stub-property>
                </port-info>
            </service-client>
        </service-ref>
    </jeus-web-dd>


22.2.3.2. WSDL 없이 서비스 호출

WS-I라는 표준 기구는 웹 서비스의 상호 호환성을 위해 Basic Profile을 정의하고 이를 준수할 것을 요구하고 있다. 현재 나와있는 Basic Profile 1.0은 WSDL이 항상 접근 가능하게 공개되기를 요구한다.

하지만, Basic Profile을 준수하지 않는 서비스에 접근하기를 원한다면 WSDL이 항상 공개되어 있다고 볼 수 없다. 이 경우 WSDL과 무관하게 웹 서비스를 호출할 수 있는데, DII(Dynamic Invocation Interface)가 바로 그 방법이다. 하지만, 이는 결국 WSDL에 포함된 오퍼레이션 스타일이라든가, 오퍼레이션 이름과 같은 상세를 Call 객체에 제공해야만 가능하다.

WSDL이 클라이언트 DD에 정의되어 있지 않다면, 클라이언트 개발자는 다음과 같은 서비스 인터페이스의 메소드를 통해서 Call 객체를 생성해야 한다.

Call createCall() throws ServiceException;

다음은 Call 객체를 이용하여 웹 서비스를 호출하는 JSP JavaEE 클라이언트의 예이다.

[예 22.10] << helloClient.jsp >>

<%@ page language="java" %>
<%@ page import="javax.naming.*" %>
<%@ page import="javax.rmi.*" %>
<%@ page import="java.rmi.RemoteException" %>
<%@ page import="java.util.*" %>

<%@ page import="javax.naming.InitialContext" %>
<%@ page import="javax.xml.rpc.Service" %>
<%@ page import="javax.xml.rpc.Call" %>
<%@ page import="javax.xml.namespace.QName" %>
<%@ page import="javax.xml.rpc.ParameterMode" %>

<%@ page errorPage="/error.html" %>

<%! String msgToSend = "msg_sent_by_jspClient";
    String ret=null;
    String exceptionString="";
%>

<%
    try {
        InitialContext jndiContext = new InitialContext();
        Service service = (Service)jndiContext.lookup(
            "java:comp/env/service/RpcEncEchoService");

        String targetNamespace = "urn:RpcEncService";
        QName operationName = new 
            QName(targetNamespace,"echoString");
        Call call = service.createCall();
        call.setOperationName(operationName);
        call.addParameter("String_1", 
            new QName("http://www.w3.org/2001/XMLSchema", "string"),
            ParameterMode.IN);
        
        call.setProperty(Call.OPERATION_STYLE_PROPERTY, "rpc");   
        call.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY, 
            "http://schemas.xmlsoap.org/soap/encoding/");

        call.setReturnType(
            newQName("http://www.w3.org/2001/XMLSchema","string"));
        call.setTargetEndpointAddress("http://localhost:8088/" +
            "RpcEncEchoService/RpcEncEchoService");

        ret = (String)call.invoke(new Object[]{msgToSend});
    } catch (Exception e) {
        exceptionString = e.toString();
        e.printStackTrace();
    }
%>
<%= "Result is "+ret+"......" 
%>


위 예제에서 제시한 대로, Call 객체를 생성한 다음, 오퍼레이션 이름과 오퍼레이션이 갖는 파라미터들을 추가하게 되며, 이때 추가적으로 오퍼레이션의 스타일과 인코딩 방식을 설정할 수도 있다.

설정 방법은 다음과 같다.

call.setProperty(Call.OPERATION_STYLE_PROPERTY, “rpc”);
call.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY, “http://schemas.xmlsoap.org/soap/encoding/”);

앞서 제시한 예제의 표준 DD(web.xml)는 다음과 같이 구성되어 있다.

[예 22.11] << web.xml >>

<?xml version="1.0"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee">
    <servlet>
        <servlet-name>jsp_helloClient</servlet-name>
        <jsp-file>/helloClient.jsp</jsp-file>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <service-ref>
        <service-ref-name>
            service/DocLitEchoService2
        </service-ref-name>
        <service-interface>
            javax.xml.rpc.Service
        </service-interface>
    </service-ref>
</web-app>


이 web.xml에는 JSP클라이언트에서 JNDI Lookup으로 찾아오는 서비스 이름이 service-ref-name element에 명시되어 있고, 서비스 인터페이스가 javax.xml.rpc.Service 인터페이스임이 명시되어 있다. WSDL이 없이 웹 서비스를 호출할 때에는 jaxrpc-mapping-file element를 이용한 JAX-RPC 매핑 파일의 설정을 사용할 수 없다.

22.2.3.3. 웹 서비스 클라이언트의 패키징

JavaEE 웹 서비스 클라이언트는 하나의 JavaEE 컴포넌트이기 때문에 컴포넌트 고유의 패키징 방식을 따른다.

앞서 제시한 JSP 클라이언트의 패키징 방식은 다음과 같다.

C:\WAR
   |
   +-- JSP Client   
   +-- WEB-INF
       |-- web.xml (서블릿 DD)
       |-- jeus-web-dd.xml (JEUS Specific DD)       
       |-- Jax-rpc mapping 파일
       |-- wsdl
           |-- wsdl 파일
       +-- classes
           +-- Java 클래스 콤포넌트 (SEI, Service Interface..)

위와 같이 패키징 한 JavaEE 클라이언트를 배치하고 JSP를 호출하면 웹 서비스를 호출할 수 있게 된다.

JAX-RPC 매핑 파일과 WSDL 파일이 webservices.xml 파일에 <wsdl-file>과 <jax-rpc-mapping-file>로 기술한 위치에 존재한다면 반드시 위의 구조를 따르지 않아도 무방하다.