핸들러 프레임워크
본 장에서는 웹 서비스의 핸들러 프레임워크에 대한 기본 개념 및 구성과 예를 통해 설정 방법에 대해 설명한다.
1. 개요
JAX-WS 웹 서비스는 핸들러를 위한 보다 쉬운 플러그인 형태의 프레임워크를 제공하는데, 이는 JEUS 9 웹 서비스의 런타임(Runtime) 시스템 기능을 보다 향상시킬 수 있다.
이러한 핸들러의 종류는 다음의 2가지로 구분할 수 있다.
-
논리적 핸들러(Logical Handler)
프로토콜에 무관하게 메시지의 페이로드(payload)에 접근할 수 있는 핸들러이다.
-
SOAP 핸들러(SOAP Handler)
Header를 포함한 SOAP 메시지 전체에 접근할 수 있는 핸들러이다.
핸들러 프레임워크는 다음과 같은 기준에 의해 사용될 수 있다.
-
전체 SOAP 메시지를 필요로 할 경우 SOAP 핸들러를 사용한다.
-
SOAP 메시지의 XML 문서 페이로드만을 필요로 할 경우에는 논리적 핸들러를 사용한다.
기타 다른 경우에는 Endpoint 클래스에서 처리하도록 웹 서비스를 구성하며 특별히 Java 오브젝트를 필요로 하는 경우에는 JEUS EJB에서 지원하는 Interceptor를 사용한다. JEUS EJB Interceptor에 대해서는 JEUS EJB 안내서를 참고한다.
2. 핸들러 체인 우선순위
핸들러 체인은 다음의 그림과 같이 와이어(Wire) 상에서 나가는(Outbound) 메시지인 경우 모든 Logical 핸들러가 SOAP 핸들러보다 앞서 처리된다. 반대로 들어오는(Inbound) 메시지인 경우 모든 SOAP 핸들러가 Logical 핸들러보다 앞서 처리된다.
즉, 클라이언트 또는 서비스 Endpoint는 프로그래밍에서 논리적 핸들러가 SOAP 핸들러보다 앞에 설정되어 있어도 결국 서비스 생성 혹은 호출하는 경우에는 모든 논리적 핸들러는 SOAP 핸들러를 앞서 처리된다. |
3. 핸들러 클래스 구성
본 절에서는 사용자가 핸들러 클래스를 구성하는 방법과 핸들러 클래스에서 사용되는 메시지 컨텍스트(MessageContext) 클래스에 대해 설명한다.
3.1. 핸들러 클래스의 선언
사용자는 핸들러 클래스를 구성하기 위해 Logical 핸들러 혹은 SOAP 핸들러 인터페이스를 구현하는 클래스를 작성한다.
다음은 각각의 핸들러 클래스의 예이다.
public class MyLogicalHandler implements LogicalHandler<LogicalMessageContext> { public boolean handleMessage(LogicalMessageContext messageContext) { LogicalMessage msg = messageContext.getMessage(); return true; } }
public class MySOAPHandler implements SOAPHandler<SOAPMessageContext> { public boolean handleMessage(SOAPMessageContext messageContext) { SOAPMessage msg = messageContext.getMessage(); return true; } }
위와 같이 Logical 핸들러, SOAP 핸들러는 공통적으로 Handler 인터페이스를 구현하고 있다. Handler 인터페이스는 handlerMessage( ) 그리고 handleFault( ) 메소드를 지니고 있다.
2가지 메소드는 공통적으로 MessageContext 클래스를 상속하는 객체를 파라미터로 넘겨 받는데, 그 객체는 현재 핸들러로 들어온 메시지가 들어오는(inbound) 것인지 나가는(outbound) 것인지를 구분하는 역할을 한다. 이러한 사용자 핸들러 클래스는 다음과 같이 @PostConstruct Annotation과 @PreDestroy Annotation을 사용할 수 있다.
public class MyLogicalHandler implements LogicalHandler<LogicalMessageContext> { @PostConstruct public void methodA() {} @PreDestroy public void methodB() {} }
위와 같이 선언된 MyLogicalHandler에서 @PostConstruct Annotation으로 선언된 메소드인 'methodA’는 이 핸들러가 생성된 후 호출된다. 또한 @PreDestroy Annotation으로 선언된 메소드인 'methodB’는 이 핸들러가 없어지기 전에 호출된다.
4. 핸들러 클래스 설정
본 절에서는 구성한 사용자 핸들러를 웹 서비스에 적용시키는 방법에 대해 설명한다.
4.1. Java 클래스로부터 웹 서비스 구성
Java 클래스로부터 웹 서비스를 구성할 때에는 서비스 Endpoint 구현 클래스에 @HandlerChain Annotation으로 설정하여 wsgen 툴을 사용해서 웹 서비스를 구성한다.
@WebService @HandlerChain( file="handlers.xml") public class MyServiceImpl { ... }
위와 같이 @HandlerChain Annotation에 설정한 handlers.xml의 모습은 아래와 같다. 실제 서버의 핸들러 설정은 handlers.xml이라는 메타 데이터 파일을 통해 설정된다.
<?xml version="1.0" encoding="UTF-8"?> <jws:handler-chains xmlns:jws="https://jakarta.ee/xml/ns/jakartaee"> <jws:handler-chain> <jws:handler> <jws:handler-class>fromjava.handler.TmaxHandler</jws:handler-class> </jws:handler> </jws:handler-chain> </jws:handler-chains>
4.2. WSDL로부터 웹 서비스 구성
WSDL로부터 웹 서비스를 구성할 때에는 웹 서비스를 구성할 WSDL 문서에 간접적으로 바인딩 사용자화를 설정하여 wsimport 툴을 사용해서 웹 서비스를 구성한다.
다음은 외부 파일을 이용하여 WSDL 문서에 간접적으로 바인딩을 사용자화하는 예이다.
<bindings xmlns="http://java.sun.com/xml/ns/jaxws"> <handler-chains xmlnx="xmlns="https://jakarta.ee/xml/ns/jakartaee"> <handler-chain> <handler> <handler-class>fromwsdl.handler.TmaxHandler</handler-class> </handler> </handler-chain> </handler-chains> </bindings>
위와 같이 바인딩 사용자화 파일에 <handler-chains>가 추가된 것에 주목한다.
다음과 같이 <handler-chains> 아래에 <handler-chain>을 여러 개 설정할 수도 있다.
<handler-chains xmlnx="xmlns="https://jakarta.ee/xml/ns/jakartaee"> <handler-chain> <service-name-pattern xmlns:tm="http://tmaxsoft.com"> tm:Tmax*Service </service-name-pattern> <handler>...</handler> </handler-chain> <handler-chain> <port-name-pattern xmlns:tm="http://tmaxsoft.com"> tm:TmaxPort </port-name-pattern> <handler>...</handler> </handler-chain> <handler-chain> <protocol-bindings>##SOAP11_HTTP</protocol-bindings> <handler>...</handler> </handler-chain> </handler-chains>
위와 같이 여러 개의 <handler-chain>이 하나의 <handler-chains>로 구성될 때에는 어떤 핸들러에 서비스 이름이나 포트 이름 또는 프로토콜과 같은 속성을 부여할 수 있다.
5. 핸들러 체인을 사용하는 웹 서비스 예제
본 절에서는 로그를 출력하는 사용자 SOAP 핸들러를 구현하여 웹 서비스에 이용하는 간단한 예제를 살펴본다.
Logical 핸들러 클래스에 속하는 jakarta.xml.ws.handler.LogicalHandler 또는 SOAP 핸들러에 속하는 jakarta.xml.ws.handler.SOAPHandler 클래스는 추상 인터페이스인 jakarta.xml.ws.handler.Handler를 상속하고 있다. 이 2가지 클래스의 구현으로 사용자가 원하는 핸들러를 생성할 수 있다.
여기서 구현할 핸들러 클래스인 'LoggingHandler’는 SOAP 핸들러인 jakarta.xml.ws.handler.soap.SOAPHandler를 구현하는 클래스이다.
public class LoggingHandler implements SOAPHandler<SOAPMessageContext> { public Set<QName> getHeaders() { return null; } public void close(MessageContext messageContext) { } public boolean handleFault(SOAPMessageContext smc) { return true; } public boolean handleMessage(SOAPMessageContext smc) { Boolean inboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); System.out.println("\n##############################################"); System.out.println("### JAX-WS Webservices examples - handler ###"); System.out.println("##############################################"); if (inboundProperty.booleanValue()) { System.out.println("\nClient message:"); } else { System.out.println("\nServer message:"); } SOAPMessage message = smc.getMessage(); try { message.writeTo(System.out); } catch (Exception e) { e.printStackTrace(); } return true; } }
위와 같이 이 핸들러에서는 핸들러로 들어오는 메시지가 서버로 들어오는 메시지인지 클라이언트로부터 나가는 메시지인지를 검사하고 출력한다.
서비스 클래스
다음은 예제의 서비스 부분 Java 클래스이다. 서비스 부분에서 @HandlerChain Annotation을 사용하여 서버의 핸들러 부분을 설정하는 모습을 확인할 수 있다.
<?xml version="1.0" encoding="UTF-8"?> <handler-chains xmlnx="xmlns="https://jakarta.ee/xml/ns/jakartaee"> <handler-chain> <handler> <handler-class>fromjavahandler.common.LoggingHandler</handler-class> </handler> </handler-chain> </handler-chains>
다음은 @HandlerChain에 등록된 파일인 handlers.xml의 예이다.
@WebService @HandlerChain(file = "handlers.xml") public class AddNumbersImpl { public int addNumbers(int number1, int number2) { return number1 + number2; } }
클라이언트 클래스
다음은 이 예제의 클라이언트 부분 Java 클래스이다. 클라이언트 부분에서는 WSDL 문서로 클라이언트를 생성할 때 사용하는 wsimport 툴에 바인딩 사용자 선언을 추가한다.
public class AddNumbersClient { public static void main(String[] args) { AddNumbersImpl port = new AddNumbersImplService().getAddNumbersImplPort(); port.addNumbers(10, 20); } }
다음은 이러한 바인딩 사용자 선언의 예이다.
<?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" xmlns:jws="https://jakarta.ee/xml/ns/jakartaee"> <jws:handler-chains> <jws:handler-chain> <jws:handler> <jws:handler-class> fromjavahandler.common.LoggingHandler </jws:handler-class> </jws:handler> </jws:handler-chain> </jws:handler-chains> </bindings> </bindings>
6. 웹 서비스의 핸들러 프레임워크 실행
본 절에서는 지금까지 구현한 클래스들 및 기타 설정 파일들을 이용하여 핸들러 프레임워크를 실행하는 방법에 대해서 설명한다. 기타 서비스 Endpoint 인터페이스의 구현 클래스 및 기타 설정 파일들은 앞 장에서 설명한 예제의 내용과 동일하다.
다음과 같이 핸들러 프레임워크를 설정한 서비스를 생성하여 JEUS에 deploy한다.
$ ant build deploy
위의 과정이 모두 실행되어 서비스가 정상적으로 deploy되면, 클라이언트를 빌드하고 호출한다. 클라이언트에서 wsimport의 과정을 거치므로 서비스의 deploy가 모두 완료되었을 때 클라이언트의 구성이 가능하다.
다음과 같이 핸들러 프레임워크를 설정한 클라이언트를 생성하고 서비스에 호출한다. 콘솔에서 입력하면 메시지를 주고받는 모습이 LoggingHandler에 의해 서비스와 클라이언트의 화면에 모두 나타나는 것을 알 수 있다.
$ ant run ... run: [java] ############################################## [java] ### JAX-WS Webservices examples - handler ### [java] ############################################## [java] Client message: [java] <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body><ns2:addNumbers xmlns:ns2="http://server.fromjavahandler/"><arg0>10</arg0> <arg1>20</arg1></ns2:addNumbers></S:Body></S:Envelope> [java] ############################################## [java] ### JAX-WS Webservices examples - handler ### [java] ############################################## [java] Server message: [java] <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Header/><S:Body><ns2:addNumbersResponse xmlns:ns2="http://server.fromjavahandler/"> <return>30</return></ns2:addNumbersResponse></S:Body></S:Envelope> ... BUILD SUCCESSFUL