Asynchronous Invocation
보통의 synchronous한 호출 방식과 함께, Apache CXF는 JAX-WS Spec.에 정의된 2가지(Polling approach, Callback approach) 형태의 비동기(asynchronous) 호출 방식을 지원한다.
즉, 클라이언트 사이드에서 서버 사이드의 Web Services를 호출 시 비동기적으로 호출하여 사용할 수 있게 하는 기능이다.
Polling approach의 특징
을 살펴보면 다음과 같다.
- 서버) 서비스 인터페이스에 메소드(메소드명: 대상 메소드 명 + "Async")를 추가 작성하는데, 이때 리턴타입이 Response인 메소드를 작성한다.
- 클라이언트) 원격에 존재하는 메소드를 호출하기 위해, output 파라미터 없이 javax.xml.ws.Response 객체를 리턴하는 특정 메소드를 호출한다.
- javax.util.concurrency.Future 인터페이스를 상속받은 Response 객체는 응답 메시지가 도착했는지 여부를 확인하는 표를 받는다.
Callback approach의 특징
을 살펴보면 다음과 같다.
- 서버) 서비스 인터페이스에 메소드(메소드명: 대상 메소드 명 + "Async")를 추가 작성하는데, 이때 리턴 타입이 Future<?>이며 AsynchHandler 파라미터를 추가로 갖는 메소드를 작성한다.
- 클라이언트) AsyncHandler 클래스 구현이 필요하다.
- 클라이언트) 원격에 존재하는 메소드를 호출하기 위해, 파라미터 중 하나가 javax.xml.ws.AsyncHandler 타입인 callback 객체에 참조 관계가 있는 위에서 작성한 메소드를 호출한다.
- 응답 메시지가 클라이언트에 도착하자마자, Apache CXF 런타임 환경은 응답 메시지의 컨텐츠에 응답 메시지를 전달해주기 위해 AsyncHandler 객체를 재호출한다.
다음은 Asynchronous Method Invocation 기능을 사용하기 위해서 Server와 Client 단에서 어떤 작업을 수행해야 하는지에 대한 내용이다.
2가지(Polling approach와 Callback approach) 형태의 asynchronous한 호출 방식에 대해서 하나의 예제를 가지고 살펴보도록 한다.
Server Configuration
Apache CXF에서 제공하는
Tool
을 이용하여 비동기적으로 Web Services의 메소드를 호출할 수 있도록 지원하고 있다.
아래 예제를 통해서 Tool 사용 모습을 포함하여 어떻게 비동기적인 호출을 가능하게 하는지 살펴보도록 한다.
Tool을 아직 준비하지 못했다면 Apache CXF를
다운로드 페이지
에서 내려 받아서 압축을 풀고 루트 폴더 하위의 bin 폴더 내에 존재하는
Tool을 사용하도록 한다.
Samples
Web Service로 노출시킬 Movie Service의 인터페이스 클래스를 대상으로 java2ws Tool을 사용하여 WSDL 파일을 생성낸다.
WSDL 파일을 생성한 이후, wsdl2java Tool을 통해 WSDL 파일과 asynch_binding.xml 파일을 이용하여 asynchronous한 호출을 가능하게 하는 Java 소스
코드들(anyframe.sample.movie.jaxws.asynch.asynch_soap_http package 하위의 클래스들(SEI, WebServiceClient, complex type classes))을 생성한다.
이때 이미 WSDL 파일을 가지고 있다면 java2ws Tool을 사용할 필요는 없다.
Temporary Interface Class
다음은 Movie Service의 인터페이스 클래스를 작성한 MovieServiceAsynch.java
의 일부이다.
JAX-WS Frontend 모델을 이용하여 Web Service를 구현하므로, 인터페이스 클래스 상단에 @WebService Annotation을 작성해줘야 한다.
인터페이스 메소드로는 findMovieListAll() 메소드를 정의하고, 클라이언트에서 asynchronous하게 이 메소드를 호출하고자 한다.
실제로는 이 인터페이스를 사용하지 않는다. WSDL 파일을 생성시키고자 하는 목적에서 작성한 것
이다. 실제로는 WSDL 파일로부터 생성된 인터페이스 클래스를 사용하게 됨에 유의하도록 한다.
@WebService(targetNamespace = "http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http")
public interface MovieServiceAsynch {
public List<Movie> findMovieListAll() throws Exception;
}
...중략
WSDL 파일 생성
인터페이스 클래스를 기준으로 WSDL 파일을 생성시키도록 한다. 이때 Apache CXF에서 제공하는 Tool 중 java2ws을 이용하여 생성시킨다.
Command Prompt 상에서 아래와 같은 명령어를 수행시키도록 한다.
수행 시키는 위치는 anyframe-remotingtest-src 프로젝트가 존재하는 루트 폴더이다.
command>java2ws -wsdl -cp target/classes anyframe.sample.movie.jaxws.asynch.MovieServiceAsynch
다음은 생성된 WSDL 파일(MovieServiceAsynchService.wsdl)
의 일부이다.
생성된 후 soap:address location 정보 등 수정하고 싶은 정보가 있는 경우 변경하도록 한다.
<wsdl:definitions name="MovieServiceAsynchService"
targetNamespace="http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http"
중략...
<wsdl:binding name="MovieServiceAsynchServiceSoapBinding" type="tns:MovieServiceAsynch">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="findMovieListAll">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="findMovieListAll">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="findMovieListAllResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="MovieServiceAsynchService">
<wsdl:port name="MovieServiceAsynchPort" binding="tns:MovieServiceAsynchServiceSoapBinding">
<soap:address location="http://localhost:9002/Movie"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
...중략
Configuration
Asynchronous 기능을 제공하는 Java 소스 코드를 생성하기 위해서는 WSDL 파일 뿐아니라 asynch_binding.xml 파일도 함께 필요하다.
다음은 asynch_binding.xml
의 일부이다.
여기서 wsdlLocation 부분에 유의하여 작성하도록 한다.
<bindings
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
wsdlLocation="MovieServiceAsynchService.wsdl"
xmlns="http://java.sun.com/xml/ns/jaxws">
<bindings node="wsdl:definitions">
<enableAsyncMapping>true</enableAsyncMapping>
</bindings>
</bindings>
중략...
Java 코드 생성
지금까지 준비된 WSDL 파일과 asynch_binding.xml 파일을 이용하여 Asynchronous 기능을 제공하는 Java 소스 코드를 생성하도록 한다.
이때 Apache CXF에서 제공하는 Tool 중 wsdl2java을 이용하여 생성시킨다.
Command Prompt 상에서 아래와 같은 명령어를 수행시키도록 한다.
수행 시키는 위치는 anyframe-remotingtest-src 프로젝트가 존재하는 루트 폴더이다.
command>wsdl2java -d src -b src/test/resources/webservices/asynch/wsdl/asynch_binding.xml
src/test/resources/webservices/asynch/wsdl/MovieServiceAsynchService.wsdl
Tool을 통해 생성되는 Java 코드들은 모두 anyframe.sample.movie.jaxws.asynch.asynch_soap_http
package 하위에 위치한다.
MovieService의 인터페이스 클래스인 MovieServiceAsynch.java
, MovieService 접근을 위한 WebService Client 클래스인 MovieServiceAsynchService.java
,
JavaBeans 클래스인 Movie.java
클래스 등 여러 가지의 Java 소스 코드가 생성된다.
다음은 생성된 인터페이스 클래스인 MovieServiceAsynch.java
의 일부이다.
이 인터페이스 클래스가 실제로 사용된다.
Polling approach 방식 및 Callback approach에서 사용되는 2개의 findMovieListAllAsync() 메소드와 추가로 생성되어 있음을 확인할 수 있다.
@WebService(targetNamespace = "http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http",
name = "MovieServiceAsynchService")
@XmlSeeAlso( {ObjectFactory.class })
public interface MovieServiceAsynch {
@ResponseWrapper(localName = "findMovieListAllResponse",
targetNamespace = "http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http",
className = "anyframe.sample.movie.jaxws.asynch.asynch_soap_http.FindMovieListAllResponse")
@RequestWrapper(localName = "findMovieListAll",
targetNamespace = "http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http",
className = "anyframe.sample.movie.jaxws.asynch.asynch_soap_http.FindMovieListAll")
@WebResult(name = "return", targetNamespace = "")
@WebMethod
public java.util.List<anyframe.sample.movie.jaxws.asynch.asynch_soap_http.Movie>
findMovieListAll() throws Exception;
@ResponseWrapper(localName = "findMovieListAllResponse",
targetNamespace = "http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http",
className = "anyframe.sample.movie.jaxws.asynch.asynch_soap_http.FindMovieListAllResponse")
@RequestWrapper(localName = "findMovieListAll",
targetNamespace = "http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http",
className = "anyframe.sample.movie.jaxws.asynch.asynch_soap_http.FindMovieListAll")
@WebMethod(operationName = "findMovieListAll")
public Response<anyframe.sample.movie.jaxws.asynch.asynch_soap_http.FindMovieListAllResponse>
findMovieListAllAsync();
@ResponseWrapper(localName = "findMovieListAllResponse",
targetNamespace = "http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http",
className = "anyframe.sample.movie.jaxws.asynch.asynch_soap_http.FindMovieListAllResponse")
@RequestWrapper(localName = "findMovieListAll",
targetNamespace = "http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http",
className = "anyframe.sample.movie.jaxws.asynch.asynch_soap_http.FindMovieListAll")
@WebMethod(operationName = "findMovieListAll")
public Future<?> findMovieListAllAsync(
@WebParam(name = "asyncHandler", targetNamespace = "")
AsyncHandler<anyframe.sample.movie.jaxws.asynch.asynch_soap_http.FindMovieListAllResponse>
asyncHandler);
}
중략...
Implementation Class
Interface Class를 구현한 구현 클래스로 2개의 findMovieListAllAsync() 메소드에 대해서 빈 내용으로 구현 메소드만 작성해놓는다. 실제로 호출되지는 않는다.
다음은 Movie Service의 인터페이스 클래스인 MovieServiceAsynch를 구현한 MovieServiceImpl.java
의 일부이다.
@WebService(serviceName = "MovieServiceAsynchService", portName = "MovieServiceAsynchPort", 중략...
public class MovieServiceImpl implements MovieServiceAsynch {
public List<Movie> findMovieListAll() throws Exception {
return this.movieDAO.findMovieListAll();
}
public Response<FindMovieListAllResponse> findMovieListAllAsync() {
return null;
/* not called */
}
public Future<?> findMovieListAllAsync(
AsyncHandler<FindMovieListAllResponse> asyncHandler) {
return null;
/* not called */
}
중략...
Anyframe JaxWsServer 사용한 서버 구동
다음은 서버 사이드의 서비스를 Web Services로 노출시키는 서버를 구동하는 코드를 작성한 JaxWsAsynchTest.java
의 일부이다.
setUp() 메소드 내에서 JaxWsServer
를 생성시킨 후, 서버의 정보로 인터페이스 클래스, 구현 클래스의 인스턴스, Web Services 주소를 설정해준다.
상위 테스트케이스 클래스인 RemotingTestCase
의 setUp() 메소드에서 Server
의 start() 메소드가 호출되면서 실제로 구동된다.
public class JaxWsAsynchTest extends RemotingTestCase {
// ==============================================================
// ====== TestCase 수행에 필요한 사전 작업 정의 ====================
// ==============================================================
public void setUp() throws Exception {
this.setServer(new JaxWsServer());
this.getServer().setServerInfo(
new ServerInfo(MovieServiceAsynch.class, new MovieServiceImpl(),
"http://localhost:9002/Movie", false, false));
super.setUp();
}
...중략
Client Configuration
클라이언트에서 서버 사이드의 Web Services를 호출 시 비동기적으로 호출하여 사용해 보도록 한다.
이때, 위에서 Tool을 통해 생성된 Java 코드 중
MovieServiceAsynchService
를 이용하여
Web Service에 접근한다.
Samples
다음은 Web Services로 노출된 Movie Service의 메소드를 비동기적으로 호출하여 사용하는 예제이다.
Callback approach 사용 시 AsyncHandler 클래스 구현
Polling approach 방식을 사용하면 부가 작성해야 하는 클래스가 없지만, Callback approach 방식을 사용하려면 AsynchHandler 클래스를
추가 구현해줘야 한다.
JaxWsAsynchTest
테스트케이스의 테스트 메소드 중 testFindMovieListAllCallback에서 사용되는
MovieAsyncHandler 클래스로 Callback 방식의 Asynchronous Method Invocation을 위해 작성한다.
javax.xml.ws.AsyncHandler 인터페이스를 implement 하며, handleReponse method를 구현하여야 한다.
다음은 AsyncHandler 클래스를 구현한 MovieAsyncHandler.java
의 일부이다.
public class MovieAsyncHandler implements AsyncHandler<FindMovieListAllResponse> {
private FindMovieListAllResponse reply;
public void handleResponse(Response<FindMovieListAllResponse> response) {
try {
System.err.println("handleResponse called");
reply = response.get();
} catch (Exception ex) {
ex.printStackTrace();
}
}
public List<Movie> getResponse() {
return reply.getReturn();
}
...중략
Test case
다음은 앞서 작성한 코드와 Tool을 통해 생성된 Java 코드들을 이용하여 Web Services로 노출된 Movie Service에 접근하는 TestCase인 JaxWsAsynchTest.java
의 일부이다.
Tool을 통해 생성된 WebService Client 코드인 MovieServiceAsynchService를 통해, Web Services로 노출된 Movie Service의 인터페이스 메소드를 asynchronous하게 호출한다.
이때 WSDL 파일과 서비스명을 이용하여 접근하도록 한다.
Movie Service를 얻은 후에는 Movie Service에서 findMovieListAllAsync() 메소드를 비동기 방식으로 호출하여 동작이 올바른지 테스트해본다.
public class JaxWsAsynchTest extends RemotingTestCase {
// ==============================================================
// ====== TestCase 수행에 필요한 사전 작업 정의 ====================
// ==============================================================
private static final QName SERVICE_NAME =
new QName("http://asynch.jaxws.movie.sample.anyframe/asynch_soap_http",
"MovieServiceAsynchService");
// ==============================================================
// ====== TestCase methods ======================================
// ==============================================================
/**
* [Flow #-2] Positive Case : Asynchronous method invocation 방식 중
* Polling approach 방법으로 List 형태의 Movie 전체 목록을 조회한다.
* @throws Exception
* throws exception which is from service
*/
public void testFindMovieListAllPolling() throws Exception {
File wsdl = new File(
"src/test/resources/webservices/asynch/wsdl/MovieServiceAsynchService.wsdl");
MovieServiceAsynchService client =
new MovieServiceAsynchService(wsdl.toURL(), SERVICE_NAME);
MovieServiceAsynch movieService = client.getMovieServiceAsynchPort();
// 1. find movie list all
Response<FindMovieListAllResponse> response =
movieService.findMovieListAllAsync();
// 2. wait for response after asynchronous method invocation
while (!response.isDone()) {
Thread.sleep(100);
}
// 3. check the movie list count
FindMovieListAllResponse reply = response.get();
assertEquals(2, reply.getReturn().size());
}
/**
* [Flow #-3] Positive Case : Asynchronous method invocation 방식 중
* Callback approach 방법으로 List 형태의 Movie 전체 목록을 조회한다.
* @throws Exception
* throws exception which is from service
*/
public void testFindMovieListAllCallback() throws Exception {
File wsdl = new File(
"src/test/resources/webservices/asynch/wsdl/MovieServiceAsynchService.wsdl");
MovieServiceAsynchService client =
new MovieServiceAsynchService(wsdl.toURL(), SERVICE_NAME);
MovieServiceAsynch movieService = client.getMovieServiceAsynchPort();
// 1. find movie list all
MovieAsyncHandler asynchHandler = new MovieAsyncHandler();
Future<?> response = movieService.findMovieListAllAsync(asynchHandler);
// 2. wait for response after asynchronous method invocation
while (!response.isDone()) {
Thread.sleep(100);
}
// 3. check the movie list count
List<Movie> reply = asynchHandler.getResponse();
assertEquals(2, reply.size());
}
...중략
Resources
다운로드
샘플 테스트 코드를 포함하고 있는 anyframe-remotingtest-src.zip 파일을 다운받은 후, 테스트 환경 설정
을 참조하여
위에서 제시한 예제 코드를 실행해 볼 수 있다. 이때 해당 프로젝트 내의 src/test/java 소스폴더 하위의 anyframe.core.remoting.webservices.asynch 패키지 하위의 테스트케이스 클래스를
JUnit Test Framework을 이용하여 수행시키도록 한다.
| Name
|
Download
|
| anyframe-remotingtest-src.zip |
Download
|
참고자료