제9장 비동기 웹 서비스

내용 목차

9.1. 클라이언트 비동기 오퍼레이션
9.1.1. 비동기 메소드를 가진 서비스 Endpoint 인터페이스 Stub을 이용 방법
9.1.2. 디스패치 인터페이스 이용하는 방법
9.2. 비동기 프로바이더(Provider)
9.2.1. 비동기 프로바이더 사용 예제
9.2.2. 비동기 프로바이더 실행

본 장에서는클라이언트의 비동기 오퍼레이션과 비동기 프로바이더를 이용한 비동기 웹 서비스에 대해서 설명한다.

9.1. 클라이언트 비동기 오퍼레이션

서비스와 클라이언트 간의 웹 서비스 호출에서 클라이언트는 서버의 응답을 받을때까지 그 스레드를 블록시키고 기다리고 있다. 이와 같은 비효율성을 개선하기 위해 JAX-WS 웹 서비스는 클라이언트의 비동기 오퍼레이션(Operation)을 제공한다. 비동기 오퍼레이션을 구성하는 한 가지 방법은 다음과 같다.

JAX-WS 웹 서비스의 클라이언트를 구성하기 위해서는 호출하고자 하는 서비스의 WSDL 파일을 이용하는데, 여기서 그 WSDL 파일을 바인딩 사용자화 선언을 통해 정적인 비동기 메소드를 가진 서비스 Endpoint 인터페이스 Stub을 만들고 그것을 구현하는 클라이언트 클래스를 구성함으로써 클라이언트의 비동기 오퍼레이션을 구성하는 방법이다.

또 다른 방법으로 “제8장 프로바이더와 디스패치 인터페이스”에서 살펴본 디스패치 인터페이스를 사용함으로써 클라이언트의 비동기 오퍼레이션을 구성하는 방법이다.

9.1.1. 비동기 메소드를 가진 서비스 Endpoint 인터페이스 Stub을 이용 방법

비동기화 wsdl:operation은 폴링(Polling)과 콜백(Callback) 메소드로 매핑이 되는데 여기서 폴링(Polling) 메소드는 javax.xml.ws.Response 인터페이스를, 콜백(Callback) 메소드는 javax.xml.ws.AsyncHandler 인터페이스를 리턴값으로 가진다. 우선 서비스의 WSDL로부터 이러한 비동기 메소드를 가진 서비스 Endpoint 인터페이스 Stub을 얻기 위해 사용하는 비동기화 바인딩 선언에 대해 알아본다.

9.1.1.1. 비동기화 바인딩 선언

WSDL에서 명시된 wsdl:operation element를 비동기화된 매핑으로 사용하기 위해서는 JEUS 웹 서비스의 wsimport와 같은 툴을 이용하여 비동기화 wsdl:operation 매핑에 기반한 서비스 Endpoint 인터페이스를 생성해야 한다. 본 절에서는 어떻게 이러한 비동기화 wsdl:operation 매핑을 생성하는지에 대해 설명한다.

비동기화 wsdl:operation 매핑을 생성하기 위해서는 wsimport 툴에 의거한 바인딩 사용자화 설정 작업을 해야 한다. 다음은 이러한 바인딩 설정 파일인 custom-schema.xml의 한 예이다.

[예 9.1] << custom-schema.xml >>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    wsdlLocation="http://localhost:8088/AddNumbers/addnumbers?wsdl"
    xmlns="http://java.sun.com/xml/ns/jaxws">
    <bindings node="wsdl:definitions">
        <enableAsyncMapping>true</enableAsyncMapping>
    </bindings>
</bindings>

위와 같이 wsdl:definitions에 대해 바인딩을 설정하면 이 WSDL 문서 내의 모든 wsdl:operation element들에 대해 비동기화 설정이 된다. 다음은 이러한 custom-schema.xml 파일을 사용하는 build.xml의 한 부분이다.

[예 9.2] << build.xml >>

...

<target name="build_client" depends="do-deploy-success, init">
    <antcall target="wsimport">
        <param name="package.name" value="async.client" />
        <param name="binding.file" value="-b ${src.conf}/custom-client.xml" />
        <param name="wsdl.file"
            value="http://localhost:8088/AddNumbers/addnumbers?wsdl" />
    </antcall>
    <antcall target="do-compile">
        <param name="javac.excludes" value="fromjava/server/" />
    </antcall>
</target>

...

이와 같이 바인딩 사용자 선언으로 wsimport 툴을 이용하여 Portable Artifact들을 생성하면 생성된 비동기 메소드들을 가진 서비스 Endpoint 인터페이스는 다음과 같은 모습을 가진다.

public int addNumbers(int number1, int number2)
    throws java.rmi.RemoteException;
public Response<AddNumbersResponse> addNumbers(int number1,
    int number2);
public Future<?> addNumbers(int number1, int number2,
    AsyncHandler<AddNumbersResponse>);

위와 같이 Response<AddNumbersResponse>와 Future<?>를 리턴값으로 갖는 메소드가 2개 생성되었다. 이는 각각 폴링(Polling) 방식과 콜백(Callback) 방식의 메소드인데 이들을 이용해서 어떻게 클라이언트 Java 클래스를 구성하는지에 대해 계속 알아보기로 하겠다.

9.1.1.2. 폴링(Polling) 메소드와 콜백(Callback) 메소드

폴링 메소드를 사용하는 클라이언트의 구성 방법

wsimport 툴로부터 얻은 비동기 서비스 Endpoint 인터페이스의 폴링 메소드는 다음과 같은 모습이다.

public Response<AddNumbersResponse> addNumbers(int number1,
    int number2);

다음은 이러한 폴링 메소드로 매핑된 메소드를 사용하여 구현한 클라이언트 웹 서비스 예의 일부분이다. 클라이언트 애플리케이션은 서비스 Endpoint 인터페이스의 비동기 폴링 메소드를 호출하게 되고 언제 결과값이 반환되는지를 확인할 것이다.

javax.xml.ws.Response<AddNumbersResponse> resp =
port.addNumbersAsync(10, 20);
while(!resp.isDone()){
}
System.out.println(resp.get().getReturn());
...

위와 같이 폴링 메소드로 매핑된 메소드는 javax.xml.ws.Response 타입의 객체를 리턴한다. 이는 java.util.concurrent.Future<T> 로부터 상속된 isDone() 메소드를 통해서 언제 이 오퍼레이션이 완료되어 결과를 반환하는지를 결정할 수 있다.

다음은 본 장에서 사용하게 될 폴링 메소드를 지원하는 클라이언트 애플리케이션 예제 코드의 일부분이다.

[예 9.3] << AddNumbersClient.java >>

public class AddNumbersClient {

    ...

    public static void main(String[] args) {
        try {
            AddNumbersImpl port = new AddNumbersService().getAddNumbersImplPort();

            // Asynchronous polling
            Response<AddNumbersResponse> resp = port.addNumbersAsync(10, 20);
            Thread.sleep(2000);
            AddNumbersResponse output = resp.get();
            System.out.println("#############################################");
            System.out.println("### JAX-WS Webservices examples - polling ###");
            System.out.println("#############################################");
            System.out
                    .printf("call webservices in an Asynchronous Polling way...");
            System.out.printf("result : %d\n", output.getReturn());

            ...

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    ...

}

콜백 메소드를 사용하는 클라이언트의 구성 방법

wsimport 툴로부터 얻은 비동기 서비스 Endpoint 인터페이스의 콜백(callback) 메소드는 다음과 같은 모습이다.

public Future<?> addNumbers(int number1, int number2,
    AsyncHandler<AddNumbersResponse>);

콜백 메소드로 매핑된 메소드는 클라이언트 개발자가 추가적인 파라미터로써 javax.xml.ws.AsynchHandler를 구현한 핸들러 객체를 제공한다. 다음은 이러한 AsynchHandler를 구현한 핸들러 객체의 예제 코드이다.

[예 9.4] << AddNumbersClient.java >>

public class AddNumbersClient {

    ...

    static class AddNumbersCallbackHandler implements
            AsyncHandler<AddNumbersResponse> {

        private AddNumbersResponse output;

        public void handleResponse(Response<AddNumbersResponse> response) {
            try {
                output = response.get();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        AddNumbersResponse getResponse() {
            return output;
        }
    }
}

위와 같이 추가적인 AsynchHandler를 구현한 핸들러 객체는 런타임에 서버로부터 그 웹 서비스 오퍼레이션의 결과를 얻을 수 있을때 handleResponse라는 메소드를 호출하게 되고 클라이언트 애플리케이션은 getResponse() 메소드를 통해 그 결과값을 얻을 수 있다.

다음은 위에서 구현한 핸들러 객체를 이용하여 서비스 Endpoint 인터페이스의 비동기 콜백 메소드를 이용하는 클라이언트 애플리케이션 예제 코드의 일부분이다. 클라이언트 애플리케이션은 서비스 Endpoint 인터페이스의 비동기 콜백 메소드를 호출하게 되고 언제 결과값이 반환되는지를 확인할 것이다.

AddNumbersCallbackHandler callbackHandler = new
    AddNumbersCallbackHandler();
Future<?> resp = port.addNumbersAsync(number1, number2,
    callbackHandler);
while(!resp.isDone()){
}
System.out.println(callbackHandler .getResponse().getReturn());

위와 같이 콜백 메소드로 매핑된 메소드는 javax.util.concurrent.Future 타입의 객체를 리턴한다. 이는 isDone() 메소드를 통해서 언제 이 오퍼레이션이 완료되어 결과를 반환하는지를 결정할 수 있다.

다음은 본 장에서 사용하게 될 서비스 Endpoint 인터페이스의 비동기 콜백 메소드를 이용하는 클라이언트 애플리케이션 예제 코드의 일부분이다.

[예 9.5] << AddNumbersClient.java >>

public class AddNumbersClient {

    public static void main(String[] args) {
        try {
            AddNumbersImpl port = new AddNumbersService().getAddNumbersImplPort();

            ...

            // Asynchronous callback
            AddNumbersCallbackHandler callbackHandler =
                new AddNumbersCallbackHandler();
            Future<?> response = port.addNumbersAsync(10, 20, callbackHandler);
            Thread.sleep(2000);

            output = callbackHandler.getResponse();
            System.out.println("#############################################");
            System.out
                    .println("### JAX-WS Webservices examples - callback ###");
            System.out.println("#############################################");
            System.out
                    .printf("call webservices in an Asynchronous Callback way...");
            System.out.printf("result: %d\n", output.getReturn());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    ...

}


9.1.1.3. 비동기화 클라이언트 실행

지금까지 구현한 클래스들 및 기타 설정 파일들을 가지고 핸들러 프레임워크를 실행하는 법은 다음과 같다.

다음과 같이 비동기화 오퍼레이션을 설정한 웹 서비스를 생성하여 JEUS에 디플로이한다.

C:\>ant build deploy

위의 과정이 모두 실행되어 서비스가 정상적으로 디플로이되면, 클라이언트를 빌드한다.

다음과 같이 핸들러 프레임워크를 설정한 클라이언트를 생성하고 클라이언트로부터 서비스를 호출한다. 다음과 같이 콘솔상에서 입력하면 폴링 방식과 콜백 방식으로 2번의 응답를 정상적으로 받는 모습을 알 수 있다.

C:\>ant run

...

run:
     [java] #############################################
     [java] ### JAX-WS Webservices examples - polling ###
     [java] #############################################
     [java] call webservices in an Asynchronous Polling way...result : 30
     [java] #############################################
     [java] ### JAX-WS Webservices examples - callback ###
     [java] #############################################
     [java] call webservices in an Asynchronous Callback way...result: 30

...

BUILD SUCCESSFUL

9.1.2. 디스패치 인터페이스 이용하는 방법

디스패치 인터페이스를 이용하여 클라이언트의 비동기 오퍼레이션을 사용하는 것은 기본적으로 위에서 살펴본 개념과 같다. 단지 생성된 디스패치 객체에서 제공하는 invokeAsync 메소드를 사용하는데 이는 다음과 같다.

Response<T> response = dispatch.invokeAsync(T);
Future<?> response = dispatch.invokeAsync(T, AsyncHandler);

위에서와 같이 invokeAsync(T)는 폴링 방식의 비동기를 지원하는 메소드이고 invokeAsync(T, AsyncHandelr)는 콜백 방식의 비동기를 지원하는 메소드이다. AsyncHandler는 콜백 방식으로 사용하기 위해 사용자 핸들러를 구현한 것이다.

9.2. 비동기 프로바이더(Provider)

앞에서 “제8장 프로바이더와 디스패치 인터페이스”“제9장 비동기 웹 서비스”에 대해 알아보았다. 웹 서비스 Endpoint는 클라이언트로부터의 메시지를 XML 수준에서 다루고자 할 때 Endpoint로 하여금 프로바이더(Provider) 인터페이스를 구현하도록 했었다. 하지만 이렇게 프로바이더 인터페이스를 구현한 Endpoint는 XML 요청을 받고 XML 응답을 보낼때까지 동기화된 상태로 invoke() 메소드를 호출하여 동작하도록 되어 있었다. 즉 요청을 받고 응답을 보낼때까지, 비즈니스 오퍼레이션을 수행하는 동안 동기화 되어 있는 Endpoint 스레드는 아래 그림의 1, 2, 3 순서와 같이 비즈니스 오퍼레이션 스레드가 모두 끝마칠때까지 기다려야만 하는 오버헤드가 있었다.

[그림 9.1] << Server-side >>

<< Server-side >>


이러한 Endpoint의 JAX-WS 런타임 스레드로 하여금 비즈니스 오퍼레이션 스레드가 모두 끝마칠때까지 기다리게 하는 것은 JAX-WS 런타임이 동작하는 기반 트랜스포트가 요청 및 응답을 비동기적으로 수행할 수 있는 트랜스포트일 때 특히 성능에 있어서 효율성의 문제점으로 다가올 수 있다. 이런 경우 JEUS 6 웹 서비스는 비동기 프로바이더(Provider) 메카니즘을 제공하고 있는데 앞 장 “제8장 프로바이더와 디스패치 인터페이스”의 프로바이더 Endpoint와 비동기라는 기능을 제외하고 거의 유사하다. 이러한 비동기 프로바이더는 위에서 설명한 스레드 지연에 따른 시스템 부하의 문제점을 위 그림의 4, 5, 6 순서와 같이 어느 정도 해소해 줄 수 있다.

9.2.1. 비동기 프로바이더 사용 예제

Endpoint에 비동기 프로바이더를 이용하여 서비스하기 위해서는 Endpoint 클래스가 com.sun.xml.ws.api.server.AsyncProvider 클래스를 구현해야 하는 것 외에는 앞 장의 “제8장 프로바이더와 디스패치 인터페이스”에서 설명한 일반적인 프로바이더 인터페이스의 구성과 동일하다.

AsyncProvider 클래스는 invoke(Source, AsyncProviderCallback<Source>, WebServiceContext)의 메소드를 제공하는데 AsyncProviderCallback<Source>를 등록하여 직접 비즈니스 오퍼레이션의 결과를 send 메소드를 이용하여 호출하면 동기화된 응답, 즉 기존의 Provider 인터페이스를 구현해서 서비스를 구성했던 것과 동일한 효과를 얻을 수 있다. 일반적인 비동기 과정을 얻기 위해서는 위의 AsyncProviderCallback<Source> 콜백 함수를 호출하지 않고 다음과 같이 프로그래밍한다.

[예 9.6] << HelloAsyncImpl.java >>

@WebServiceProvider(wsdlLocation = "WEB-INF/wsdl/hello.wsdl",
    targetNamespace = "http://tmaxsoft.com", serviceName = "Hello",
    portName = "HelloAsyncPort")
public class HelloAsyncImpl implements AsyncProvider<Source> {

    public void invoke(Source source, AsyncProviderCallback<Source> callback,
            WebServiceContext ctxt) {
        try {
            DOMResult dom = new DOMResult();
            Transformer trans = TransformerFactory.newInstance()
                    .newTransformer();
            trans.transform(source, dom);

            Node node = dom.getNode().getFirstChild().getFirstChild()
                    .getFirstChild();
            String str = node.getNodeValue();
            new Thread(new RequestHandler(callback, str)).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private class RequestHandler implements Runnable {

        final AsyncProviderCallback<Source> callback;

        final String hello;

        public RequestHandler(AsyncProviderCallback<Source> callback,
                String hello) {
            this.callback = callback;
            this.hello = hello;
        }

        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
                return;
            }
            try {
                String body =
                    "<helloResponse xmlns=\"http://tmaxsoft.com\"><return>"
                    + hello + "</return></helloResponse>";
                callback.send(new StreamSource(new ByteArrayInputStream(body
                        .getBytes())));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


위와 같이 비동기 프로바이더를 사용하기 위해 invoke 메소드는 단지 새로운 비즈니스 오퍼레이션 스레드를 생성하기만 한다. 그러면 JAX-WS 런타임 스레드는 이후 핸들러 체인과 같은 여러 과정들을 블로킹(blocking)하지 않고 수행하게 되며 이후 비즈니스 스레드가 종료되고 얻은 결과를 가지고 다시 JAX-WS 런타임 스레드에서 이를 메시지로 구성하여 클라이언트에 전달하게 된다.

9.2.2. 비동기 프로바이더 실행

위와 같이 구성한 비동기 프로바이더의 Endpoint 클래스들 및 기타 WSDL 파일들을 가지고 서비스를 구성하여 JEUS 6에 디플로이 하는 방법은 다음과 같다.

C:\>ant build deploy

위의 과정이 모두 실행되어 서비스가 정상적으로 디플로이 되었으면, 클라이언트를 실행한다.

C:\>ant run

...

run:
     [java] ###################################################
     [java] ### JAX-WS Webservices examples - AsyncProvider ###
     [java] ###################################################
     [java] Result: Hello, Tmaxsoft!

BUILD SUCCESSFUL