웹 2.0 시대를 선도할 자바 웹 서비스 2.0

2008. 5. 10. 20:03Java

웹 서비스라는 용어가 유행어처럼 널리 퍼진지도 이미 몇 년이 흘렀다. 하지만 그 높은 관심에 비해 적용도는 그다지 높지 않았다고 말할 수 있는데, 그 이유에는 여러 가지가 있었겠지만, 그 중의 하나를 꼽자면 기술적인 성숙도가 되겠다. 또 한가지는 설정의 어려움과 같은 개발 작업의 난해함과 같은 문제를 들 수 있겠다. 기술적인 성숙도의 문제는 해를 거듭해가면서 새로운 표준과 기술들을 내놓으면서 많이 해결되었으나, 설정의 어려움과 같은 문제는 여전히 남아 있었다. 하지만 자바 웹 서비스 2.0이라는 새로운 웹 서비스 모델은 이런 설정의 어려움을 해결함과 동시에 여러 새로운 기능들이 추가 됨으로써 새로운 도약을 할 수 있는 기반을 마련하였다.

예제와 자바 EE 5 애플리케이션 서버

이번 특집에 게재된 샘플들은 기본적으로 자바EE 5 애플리케이션 서버에서 동작한다. 샘플들 중 일부분에서 특정 벤더에서 제공하는 기능을 위주로 설명한 부분도 있지만 그러한 것들은 개발 및 배치 작업의 편의상 벤더에서 제공하는 기능을 사용하였을 뿐이다. 기본적으로는 JAX-WS 본연의 특성을 설명하는데 있어 영향을 미치지는 않는다. 현재 자바EE 5 호환 인증을 세계 최초로 통과한 제우스(JEUS) 6.0 프리뷰를 활용할 수 있으며 썬마이크로시스템즈(이하 썬)의 자바EE 5 SDK 도 사용할 수 있다. 하지만 사용자 편의성 측면 때문에 제우스 6.0에 일부 의존성이 있는 내용이 있으니 제우스 6.0을 활용한다면 더 편리할 것이다.

새로운 웹 서비스 모델 - 자바 웹 서비스 2.0

새롭게 재구성된 자바EE 5 웹 서비스의 가장 핵심은 JAX-WS 2.0이다. JAX-WS 2.0은 웹 서비스에 있어서 기존의 JAX-RPC를 대체하는 수단으로 설계되었으며 이제 막 그 위용을 드러내게 되었다. 하지만 왜 기존의 JAX-RPC라는 이름을 버리고 JAX-WS라는 이름을 택해야 했을까? 처음에는 JAX-WS 2.0은 JAX-RPC 2.0이라는 이름을 가지고 시작되었고 JAX-RPC 1.1과의 하위 호환성을 지원하도록 설계가 시작되었다. 그런데 개발 과정에서 여러 문제에 봉착해 결국 하위 호환성을 포기하고 이름 또한 JAX-WS로 바뀌게 된 것이다. 결국 JAX-RPC와 JAX-WS는 기술적으로는 완전히 다른 의미를 지니게 되었다. 이렇게 전혀 다른 모습으로 등장한 JAX-WS의 배경에는 JAXB 2.0의 등장이 있다. JAXB 2.0은 모든 XML 스키마 타입을 완전히 지원하게 됨으로써 기존에 존재했던 자바 타입과 XML 타입간의 매핑을 보다 명확하게 정의할 수 있게 되었다. 기존에 JAX-RPC 스펙에 존재하던 자바 타입과 XML 타입간의 매핑에 대한 의존성을 제거할 수 있게 하는 원동력이 되었다. 여기에다 SOAP 1.2 메시지를 직접 다룰 수 있게 하는 SAAJ 1.3까지 포함하여, 자바 웹 서비스 2.0이라는 새로운 웹 서비스 모델이 등장하게 된 것이다.

자바 웹 서비스 2.0의 핵심 JAX-WS 2.0

앞에서 언급했듯이 자바 웹 서비스 2.0은 JAX-WS 2.0, JAXB 2.0, SAAJ 1.3이라는 완전히 새로운 라인업으로 구성되어 있으며 이들의 한 가운데 이번 주제에서 다루려고 하는 JAX-WS 2.0이 존재한다. 우선 자바 웹 서비스 2.0의 핵심인 JAX-WS 2.0이 기존의 JAX-RPC 1.1에 비해 어떻게 달라졌는지 중요한 것들 위주로 살펴보자.

JAXB 2.0의 지원

먼저 눈에 띄는 차이는 JAX-RPC에서 담당하던 XML 타입과 자바 타입간의 매핑 처리를 JAXB 2.0에서 처리하도록 일임한 것이다. 기존에 JAX-RPC 스펙에서는 껄끄러운 자바 타입과 XML 타입간의 매핑에 대한 정의를 스펙 안에 포함하고 있었다. 이러한 작업은 JAX-RPC 스펙에 작지 않은 부담을 주고 있었다. 이는 JAX-RPC 스펙이 완성될 즈음에 XML 스키마를 100% 지원하는 JAXB 2.0이 존재하지 않았기 때문이다. JAXB 2.0이 완성되어 세상에 나온 이상 더 이상 자바 타입과 XML 타입간의 매핑을 중복하여 정의할 필요가 없게 되었다. 따라서 자바 타입과 XML 타입의 매핑은 JAXB에 모두 맡기게 되었다. 그로 인해 아쉬운 점도 생겼다. 부수적으로는 더 이상 ‘SOAP 인코딩’ 방식의 웹 서비스를 지원하지 않게 되었으며, 웹 서비스간에 주고 받는 메시지는 모두 XML 스키마로 정의되는 ‘Literal’ 방식의 웹 서비스만을 지원하기 때문이다. 하지만 이러한 사실조차도 웹 서비스 구현 벤더간의 상호 운영성을 저해하는 잠재적인 요소를 미연에 제거한다는 사실을 고려하면 긍정적인 측면도 있다.

어노테이션(Annotation)의 활용

JAX-WS 2.0은 JDK 1.5에서부터 지원되는 어노테이션을 적극 활용하여 웹 서비스 엔드포인트의 작성과 클라이언트를 작성할 때, 자바 타입과 WSDL간의 매핑을 명시적으로 정의할 수 있게 하였고 이를 런타임 시에도 활용할 수 있게 하였다. 또한 기존의 J2EE 1.4 엔터프라이즈 웹 서비스를 생성하기 위해 번거롭게 작성해야만 했던 웹 서비스 배치 서술자 같은 것은 모두 어노테이션으로 대체 가능하게 되었다. 이제껏 문제시 되었던 웹 서비스 작성에 존재했던 번거로움을 크게 경감시켜주는 절대적인 역할을 하게 되었다. 실제로 웹 서비스에 가장 큰 진입 장벽 중의 하나는 무엇보다 번거로운 설정 작업이었으며, 이러한 어려움을 어노테이션을 통해 크게 해결하게 된 것이다.

XML/HTTP 바인딩의 지원

기존 JAX-RPC에서는 SOAP/HTTP를 기본으로 지원 하였으나, JAX-WS에서는 SOAP 형태의 메시지 뿐 아니라 XML 형태의 메시지 자체를 웹 서비스 소비자와 서비스간에 주고 받아 처리할 수 있도록 하였다. 이 밖에도 바이너리 데이터의 효과적인 전송을 위해 MTOM (SOAP Message Transmission Optimization Mechanism)의 지원, 웹 서비스 클라이언트에서의 비동기 호출 API의 지원 등의 많은 변화가 있으나 지면 관계상 이 특성들의 소개는 다음 기회로 미루기로 한다.

JAX-WS 2.0에 기반한 웹 서비스의 생성

서론이 길었다. 그만큼 많은 변화가 있었다는 반증이기도 한데, 직접 웹 서비스를 생성하고 호출하는 작업을 소개하여 그 이해를 도울까 한다. JAX-WS 2.0에서는 기존의 J2EE 1.4 환경에서와 마찬가지로 두 가지의 웹 서비스 생성 방식을 제공한다. 하나는 자바 클래스 타입으로부터 시작하는 From Java 방식이고 또 다른 하나는 이미 정의되어 있는 WSDL로부터 시작하는 From WSDL 방식이다. 이 두 가지 방식으로 웹 서비스를 생성하기 위해서는 JAX-WS 2.0에서 어떻게 해야 하는 지 알아보자.

From Java 방식의 웹 서비스 생성

From Java 방식의 웹 서비스 생성은 기본적으로 다음과 같은 절차를 따른다.

1. 웹 서비스 어노테이션이 포함된 서비스 구현 빈의 작성
2. 타 벤더간에 이식 가능한 산출물(Portable Artifact)의 생성
3. 웹 서비스 패키징과 배치

이와 같은 절차를 좀 더 살펴보면 전반적으로는 기존의 JAX-RPC 환경에서 생성한 산출물을 기업용 웹 서비스로 공개하기 위해 꼭 필요했던 배치 서술자의 작성 등 번거로운 작업이 생략 가능하게 되었음을 알 수 있다.

● 서비스 구현 빈(Service Implementation Bean)작성
그럼 먼저 서비스 구현 빈(Service Implementation Bean)의 작성 방식부터 먼저 살펴보자. 서비스 구현 빈을 작성할 경우에는 몇 가지의 필수 제약 조건이 따르게 되는데, 이러한 필수 제약 조건들은 다음과 같다.

- javax.jws.WebService 어노테이션을 포함시켜서 이 클래스가 서비스 구현 빈임을 명시한다.
- 웹 서비스 메소드의 인자와 리턴 타입은 JAXB 2.0의 자바와 XML 스키마 간의 매핑 정의와 호환되어야 한다.
- 웹 서비스 메소드의 인자와 리턴 타입은 java.rmi.Remote 인터페이스를 직접 혹은 간접적으로 구현하지 말아야 한다.

<리스트 1>JAX-WS 2.0의 서비스 구현 빈
package fromjava.server;

import javax.jws.WebService;

// JEUS에서 제공하는 웹 서비스 엔드포인트 설정을 위한 어노테이션
import jeus.webservices.annotation.EndpointDescription;

// 이 클래스가 웹 서비스 엔드포인트 임을 명시함
@WebService
// 이 웹 서비스 엔드포인트의 URL이 "HelloService" 가 되도록 설정함
@EndpointDescription(endpointUrl="HelloService")

public class HelloServiceImpl {

// 웹 서비스 메소드. 스트링 값을 받아서 리턴한다.
public String hello(String sHello){
return sHello;
}
}

이와 같은 요소들 말고도, javax.jws 패키지에 정의되어 있는 여러 어노테이션을 활용하면 메소드의 인자와 리턴 타입, 바인딩 방식 등의 커스터마이징이 가능하다. 그럼 간단한 예제 코드를 통해 실제 서비스 구현 빈이 어떻게 작성되어야 하는지 살펴 보자. <리스트 1>은 간단한 웹 서비스를 구현한 클래스의 예이다. JAX-WS 2.0에서는 기존 자바 클래스를 웹 서비스로 공개하기가 아주 쉽다. <리스트 1>에서와 같이 @WebService 어노테이션을 추가함으로써 쉽게 기존 자바 클래스를 웹 서비스로 변환하여 만들 수 있다. @WebService 어노테이션 이외에도 부가적인 웹 서비스의 엔드포인트도 설정할 수 있다. @Endpoint Description이라는 자바 클래스가 웹 서비스로 배치되었을 경우 공개되는 웹 서비스 엔드포인트 URL을 “HelloService”와 같이 특정한 값으로 설정할 수 있다(@EndpointDescription는 제우스 6.0에서 제공하는 어노테이션이다). 이는 기존의 JAX-RPC를 채용한 J2EE 1.4 웹 서비스 환경에서는 web.xml과 같은 배치 서술자에 추가적인 설정을 해 주었어야만 가능 했던 것이다. 하지만 자바 EE 5 웹 서비스 환경에서는 별도의 web. xml 같은 배치 서술자를 작성할 필요가 없으며 추가적인 설정은 앞에서와 같이 웹 서비스 어노테이션을 서비스 구현 빈만 추가하면 된다.

● 타 벤더 간에 이식 가능한 산출물(Portable Artifact)의 생성
서비스 구현 빈을 작성하고 컴파일 까지 수행했다면, JAX-WS 런타임에서 사용할 타 벤더 간에 이식 가능한 산출물을 생성해야 한다. 여기서 타 벤더 간에 이식 가능한 산출물이란, JAX-WS 스펙을 준수하는 모든 벤더에서 사용할 수 있는 JAX-WS 툴을 통해 만들어낸 산출물을 의미한다. 자바의 파라미터를 실제 WSDL의 메시지로 정확하게 매핑하기 위한 정보를 담고 있는 자바 클래스와 WSDL등이 여기에 포함된다. 제우스 6.0에서는 wsgen이라는 컨솔 스크립트를 제공하는데 이 스크립트는 %JEUS_HOME%/bin 디렉토리 밑에 존재한다.

<리스트 2>JAX-WS 툴을 이용한 이식 가능한 산출물의 생성
wsgen -cp < classpath > -d < destination_directory> fromjava.server.HelloServiceImpl

<리스트 2>와 같이 명령을 실행하면 지정한 경로에 이식 가능한 산출물이 생성되는 것을 확인 할 수 있을 것이다. 위 스크립트를 실행할 때 -wsdl 옵션을 주면 WSDL까지 생성할 수 있다. JAX-WS 2.0에서는 웹 서비스 엔드포인트에 WSDL을 포함시키지 않아도 되므로 여기서 -wsdl 옵션을 주지 말아야 한다. 다음은 이 산출물들을 묶어서 서버에 배치하는 작업을 진행해 보자.

● 웹 서비스 패키징과 배치
현재 작성하고 있는 웹 서비스를 패키징 한다는 것은 서비스 구현 빈, 서비스 구현 빈이 참조하고 있는 자바 클래스와 부수적인 배치 서술자들을 WAR 형식으로 묶는 것을 의미한다. 여기서는 이미 앞에서 작성했던 파일인 fromjava.server.Hello ServiceImpl 클래스와 wsgen 스크립트를 통해 생성했던 fromjava.server.jaxws.Hello, fromjava.server.jaxws. HelloResponse 클래스만을 포함하면 된다. 이 클래스들은 WEB-INF/classes 밑에 포함시키면 된다. 패키징 할 파일 이름을 HelloService.war로 하여 WAR로 패키징 한 뒤에 제우스 6.0에 배치하려면, %JEUS_HOME%/webhome/deploy_home에 WAR 패키징을 복사해 두기만 하면 된다. 그러면 실제 이 서비스를 호출 할 수 있는 HTTP상의 주소는 다음과 같이 된다. http://localhost:8088/HelloService/HelloService 실제 이 주소로 웹 브라우저 상에서 호출해보면 <화면 1>과 같이 성공적으로 웹 서비스가 되는 것을 확인할 수 있다.


<화면1> 성공적으로 배치된 HelloService


● EJB 엔드포인트 웹 서비스의 생성
자바EE 웹 서비스 엔드포인트는 엔드포인트가 구동되는 환경에 따라 서블릿 엔드포인트 웹 서비스와 EJB 엔드포인트 웹 서비스로 나눌 수 있다. 즉 EJB 엔드포인트 웹 서비스는 웹 서비스의 비즈니스 로직이 무상태 세션 빈에 구현되어 있는 경우의 웹 서비스를 말한다. 짧게 줄여서 EJB 웹 서비스라고도 한다. JAX-WS 2.0에서의 EJB 웹 서비스 생성 방식은 서블릿 웹 서비스의 생성 방식과 유사하다. <리스트 3>은 EJB 형태의 서비스 구현 빈이다. 여기에서 알 수 있듯이 @WebService 어노테이션만 추가하면 기존의 무상태 세션 빈 클래스가 웹 서비스로 공개 될 수 있다. 이는 기존의 J2EE 1.4 환경에서 EJB 웹 서비스를 생성하기 위해서 ejb-jar.xml, webservices.xml과 같은 배치 서술자들을 필수적으로 작성해야 했던 것에 비해서는 괄목할 만한 변화이다.


<리스트 3>EJB 웹 서비스의 구현 - HelloServiceEJB.java
package fromjava.server;

import javax.jws.WebService;
import javax.ejb.Stateless;
import jeus.webservices.annotation.EndpointDescription;

// 이 EJB 빈 클래스가 웹서비스 엔드포인트임을 명시한다.

@WebService
// JEUS 6.0에서 제공하는 기능으로 HTTP상에서 접근 가능한 경로가 설정 가능하다.
@EndpointDescription(contextPath="webservice", endpointUrl="HelloService")

// 이 클래스가 무상태 세션 빈 클래스임을 명시한다.
@Stateless
public class HelloServiceEJB {

// 실제 웹 서비스 메소드로 공개되는 부분이다.
public String hello(String sHello){
return sHello;
}
}

이렇게 작성된 EJB 빈 클래스를 일반 EJB 3.0의 패키징 방식으로 묶어서 배치하면 다음 주소로 이 웹 서비스에 접근할 수 있다.
http://localhost:8088/webservice/HelloService


<화면2> 성공적으로 배치된 HelloServiceEJB


From WSDL 방식의 웹 서비스 생성

From Java 방식의 웹 서비스 방식이 이미 작성한 자바 RPC 모델을 웹 서비스로 공개하는 것이 초점이다. 한편, From WSDL 방식의 웹 서비스는 이미 서로간에 통신할 SOAP 메시지를 먼저 정의하고 WSDL를 통해 그 정보를 공유한 뒤에 정의된 메시지 타입에 맞도록 자바 클래스를 생성하는 것이 초점이라 할 수 있다. 일반적인 From WSDL 방식의 웹 서비스 생성은 다음과 같은 절차를 따른다. 그럼 이런 절차에 따라서 직접 From WSDL 방식의 웹 서비스를 생성해 보도록 하자.

1. 서비스 엔드포인트 인터페이스(Service Endpoint Interface)의 생성
2. 서비스 엔드포인트 인터페이스의 구현
3. 웹 서비스 패키징과 배치


● 서비스 엔드포인트 인터페이스(Service Endpoint Interface)의 생성

이 과정에서는 이미 공개되어 있는 WSDL을 기반으로 웹 서비스의 자바 인터페이스 파일과 자바 인터페이스 파일이 사용하는 자바 타입 클래스 파일들을 생성한다. 이 때 제우스 6.0에서 제공하는 wsimport라는 컨솔 스크립트를 사용하면 되고, 필요한 스크립트는 %JEUS_HOME%/bin에 있다.


<리스트 4>JAX-WS 툴을 이용한 서비스 엔드포인트 인터페이스의(SEI) 생성
// 이 경우, WSDL 파일은 ./wsdl 이라는 경로에 존재하는 것으로 가정하며,
// 생성된 SEI는 fromwsdl.server 패키지 이름으로 생성되게 된다
wsimport -keep -p fromwsdl.server -d ./build ./wsdl/HelloServiceImplService.wsde

<리스트 4>와 같이 명령을 실행하면, 지정한 경로에 서비스 엔드포인트 인터페이스와 서비스 정의 클래스를 포함한 여러 산출물들이 만들어진다. 이 중, 생성된 서비스 엔드포인트 인터페이스는 <리스트 5>와 같이 보이게 될 것이다. 이렇게 생성된 서비스 엔드포인트 인터페이스는 JAX-WS 2.0 런타임에 사용될 정보들을 어노테이션 형태로 포함하고 있다.
@WebService 어노테이션은 이 인터페이스가 웹 서비스 엔드포인트로 사용되고 있음을 나타내게 되고, @WebMethod 어노테이션은 이 어노테이션이 붙은 메소드가 웹 서비스로 공개 될 비즈니스 메소드임을 나타낸다. WSDL에 정의 되어 있는 메시지의 스키마 타입은 JAXB 2.0의 자바로의 매핑 법칙에 의해 특정한 자바 클래스로 생성이 되었다. 이들 파라미터와 리턴 타입을 서비스 엔드포인트 인터페이스에 이어 주는 역할을 하는 것이 바로 @RequestWrapper와 @Response Wrapper이다.

<리스트 5>JAX-WS 툴을 이용해 생성한 서비스 엔드포인트 인터페이스(SEI)
package fromwsdl.server;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;

// 이 생성된 인터페이스가 웹 서비스 엔드포인트임을 명시한다.
@WebService(name = "HelloServiceImpl",
targetNamespace = "http://server.fromjava/",
wsdlLocation = "./wsdl/HelloServiceImplService.wsdl")

public interface HelloServiceImpl {

/**
*
* @param arg0
* @return
* returns java.lang.String
*/
// 이 메소드가 웹 서비스 메소드로 공개될 메소드임을 명시한다.
@WebMethod

@WebResult(targetNamespace = "")
// 웹 서비스와 웹 서비스 소비자 간의 주고 받는 메시지 타입에 관한 정의이다.
@RequestWrapper(localName = "hello",
targetNamespace = "http://server.fromjava/",
className = "fromwsdl.server.Hello")
@ResponseWrapper(localName = "helloResponse",
targetNamespace = "http://server.fromjava/",
className = "fromwsdl.server.HelloResponse")

public String hello(
//주고 받는 웹 메소드의 파라미터가 SOAP 메시지 안에서 실제로 표기되는 방식을 정의한다.
@WebParam(name = "arg0", targetNamespace = "")
String arg0);


● 서비스 엔드포인트 인터페이스의 구현
서비스 엔드포인트 인터페이스가 만들어졌다면, 다음 단계는 이 엔드포인트 인터페이스를 구현하는 실제 비즈니스 로직을 가지고 있는 서비스 구현 빈을 작성하는 것이다. 서비스 엔드포인트 인터페이스를 구현한 서비스 구현 빈을 작성할 때에는 @WebService 어노테이션을 추가해야 한다. 이 어노테이션은 서비스 엔드포인트 인터페이스를 명시한 endpointInterface 멤터를 속성으로 가지고 있어야 한다. <리스트 6>에서는 실제로 서비스 엔드포인트 인터페이스를 구현한 서비스 구현 빈을 보여준다. 여기서도 @EndpointDescription이라는 어노테이션을 추가하여 HTTP 상에 공개할 엔드포인트 URL을 임의의 값으로 지정할 수 있다.


<리스트 6>서비스 엔드포인트 인터페이스를 구현한 서비스 구현 빈
package fromwsdl.server;

import javax.jws.WebService;
import jeus.webservices.annotation.EndpointDescription;

// 이 클래스가 실제로 웹 서비스의 엔드포인트 임을 명시하며, 보다 상세한 웹 서비스에 대한
// 서술은 적시한 서비스 엔드포인트 인터페이스를 참조할 것을 명시한다.
@WebService(endpointInterface="fromwsdl.server.HelloServiceImpl")

// 서비스에 접근하기 위해서 필요한 URL을 명시적으로 설정한다.
@EndpointDescription(endpointUrl="HelloServiceTopdown")
// wsimport를 통해 생성하였던 서비스 엔드포인트 인터페이스 HelloServiceImpl를 구현하고 있다.
public class HelloServiceImplSoapBinding implements HelloServiceImpl{

// 실제 구현된 웹 서비스 메소드다.
public String hello(String sHello){
return sHello;
} }


여기까지 해서 서비스 구현 빈까지 작성했다면 이를 웹 서비스로 공개하는 작업을 진행해보자.

● 웹 서비스 패키징과 배치
웹 서비스로 공개하기 위해 WAR 형태로 패키징 하는 작업은 이전의 From Java 방식의 웹 서비스 패키징 및 배치 작업과 유사하다. wsimport 스크립트를 통해 생성한 서비스 엔드포인트 인터페이스를 포함한 몇 가지의 산출물들과 서비스 구현 빈을 WEB-INF/classes에 두고 WAR 형태로 패키징 하면 된다. 이 때의 WAR 패키지의 이름을 HelloServiceTopdown.war라고 하였을 경우, 이를 제우스 6.0에 배치하면 다음과 같은 주소로 접근할 수 있게 된다.
http://localhost:8088/HelloServiceTopdown/ HelloServiceTopdown
실제 이 주소로 웹 브라우저 상에서 호출하면 다음과 같이 성공적으로 웹 서비스가 배치되는 것을 확인할 수 있다.
지금까지 From Java 방식과 From WSDL 방식의 웹 서비스 생성을 JAX-WS 2.0 환경에서 진행하는 방법을 예제와 함께 알아 보았다. J2EE 1.4 환경에서 웹 서비스 작성 작업을 진행한 경험이 있는 독자들은 이 작업들이 얼마나 간소해졌는지 쉽게 알 수 있었을 것이다.

웹 서비스 클라이언트의 작성과 웹 서비스 호출

웹 서비스에 접근하려면 먼저 웹 서비스 클라이언트를 작성하여야 한다. 웹 서비스 클라이언트 프로그램은 구동 환경에 따라 자바SE 클라이언트와 자바EE 클라이언트로 구분할 수 있다. 자바SE 클라이언트는 일반 자바 프로그램과 마찬가지로 구동되며 자바EE 클라이언트는 EJB나 웹 컨테이너 같이 자바EE 환경에서 구동된다는 특징이 있다. 먼저 간단한 자바SE 클라이언트의 작성법부터 살펴보자.

Java SE 클라이언트의 작성

JAX-WS 2.0에서는 기본적으로 웹 서비스 엔드포인트에 대응하는 동적 프록시를 내부적으로 생성하여 마치 이를 서비스 엔드포인트 인터페이스의 구현체인 것처럼 사용할 수 있다. 따라서 클라이언트 프로그램 개발자는 생성된 프록시를 통해 서비스 엔드포인트 인터페이스로 정의된 웹 서비스 메소드를 호출 할 수 있게 되는 것이다. 이와 같은 작업을 진행하려면 서비스 엔드포인트 인터페이스와 같은 몇 가지의 산출물들이 필요하다. 이 때 필요한 산출물들을 WSDL로부터 만들어 내기 위해서는 From WSDL 방식의 웹 서비스 생성에서와 마찬가지로 wsimport라는 스크립트 툴을 사용해야 한다. <리스트 7>은 그 사용 예이다.

<리스트 7>JAX-WS 툴을 이용한 서비스 엔드포인트 인터페이스의(SEI) 생성
// 클라이언트에서 필요한 SEI와 서비스 클래스가 생성이 된다.
wsimport -p fromjava.client.generated -d ./build http://localhost:8088/HelloService/HelloService?wsdl

서비스 엔드포인트 인터페이스와 서비스 클래스가 생성 되었다면, 이제 이를 사용하는 클라이언트 자바 프로그램을 작성한다. <리스트 8>은 웹 서비스 클라이언트 프로그래밍의 한 예를 보여준다. 이 부분은 기존 JAX-RPC에서 제공하던 웹 서비스 클라이언트 프로그래밍 예와 흡사하다. 기본적으로 서비스 인스턴스를 생성한 다음, 그 서비스 인스턴스로부터 서비스 엔드포인트 인터페이스를 구현한 프록시 객체를 얻어오는 작업이 주가 된다. 클라이언트 런타임 내부적으로는 많이 달라졌지만 어쨌든 겉으로 봐서는 자바SE 클라이언트에서는 유사하다고 할 수 있겠다.

<리스트 8>HelloService를 호출하기 위한 Java SE 클라이언트
자바 프로그램
package fromjava.client;

import fromjava.client.generated.*;
public class HelloClient {
public static void main (String[] args) {
try {

// 서비스 객체를 생성하고, 동적 프록시를 생성한다.
HelloServiceImpl port = new HelloServiceImplService().getHelloServiceImplPort();

System.out.println("Invoking HelloService");

// HelloService 웹 서비스를 호출한다.
String result = port.hello("Hi~ Micro Software");
System.out.println ("Following value has returned : "+result);
} catch (Exception ex) {
}
}
}

<리스트 8>과 같이 클라이언트 프로그램을 작성하였다면 실제 이를 컴파일 하여 실행시켜서 <리스트 9>와 같은 결과를 확인할 수 있다. 성공적으로 마쳤다면 다음으로 넘어가 보자.


<리스트 9>서비스 호출 결과
Invoking HelloService
Following value has returned : Hi~ Micro Software

자바EE 클라이언트의 작성

자바EE는 클라이언트 프로그램의 구동 환경은 자바EE 환경이다. 크게는 서블릿 형태의 클라이언트와 EJB 형태의 클라이언트를 예로 들 수 있다. J2EE 1.4 환경에서의 웹 서비스 클라이언트는 web.xml이나 ejb-jar.xml에 항목을 추가하여 웹 서비스 클라이언트 프록시 생성에 필요한 정보를 JNDI에 등록할 수 있었다. JAX-WS 2.0에서는 @WebServiceRef 어노테이션만 설정하면 그와 같은 작업을 할 수 있다. <리스트 10>을 살펴보자.


<리스트 10>HelloService를 호출하기 위한 서블릿 프로그램
package fromjava.client;

import javax.xml.ws.WebServiceRef;

import javax.servlet.http.*;
import fromjava.client.generated.*;

public class HelloJ2EEClient extends HttpServlet{

// @WebServiceRef 어노테이션을 추가하면 이 서블릿이 배치 되는 시점에 이 서블릿 클래스의
// svc라는 필드에 서비스 인터페이스의 인스턴스가 채워지게 된다.
@WebServiceRef(wsdlLocation="http://localhost:8088/ HelloService/HelloService?wsdl")
static HelloServiceImplService svc;

public void doGet(HttpServletRequest request, HttpServletResponse response) {
try{
HelloServiceImpl port = svc.getHelloServiceImplPort();

System.out.println("Invoking HelloService");
String result = port.hello("Hi~ Micro Software");
System.out.println ("Following value has returned : "+result);

}catch(Exception ex){
}
}
}

<리스트 8>과 <리스트 10>의 소스 코드를 비교해 보면 알 수 있듯이 두 클라이언트 프로그램의 내용은 거의 유사하다. 기존의 J2EE 1.4 환경에서의 클라이언트 작성할 때에는 별도의 배치 서술자를 작성해야 하는 번거로움이 있었다. JAX-WS 2.0에서는 @WebServiceRef 어노테이션을 추가함으로써 그러한 번거로움을 크게 줄일 수 있게 되었다.
이 서블릿을 구동하려면 WAR 형태로 패키징해야 하는데, 이때 서블릿에 관한 정보를 정의하는 web.xml이 필요하다.


<리스트 11>서블릿 웹 서비스 클라이언트의 설정 -web.xml
< ?xml version="1.0" encoding="UTF-8"? >
< web-app … >
< description>Servlet Client for Hello Service
< display-name>Servlet HelloClient

< servlet>
   < description> Endpoint for Servlet Hello Web Service
   < display-name> Servlet HelloClient>
   < servlet-name> HelloClient
   < servlet-class> fromjava.client.HelloJ2EEClient
   < load-on-startup> 0
< /servlet>

< servlet-mapping>
   < servlet-name>HelloClient
   < url-pattern>/HelloClient
< /servlet-mapping>
< /web-app>


 


웹 서비스 어노테이션

지금까지 기본적으로 웹 서비스 어노테이션을 활용하여 웹 서비스 엔드포인트를 생성하거나 클라이언트를 작성하는 방법들에 대해 알아보았다. 이렇듯 JAX-WS에서 어노테이션은 기존의 JAX-RPC 환경에서 수행해야 했던 작업량을 상당히 줄여주는 중요한 역할을 담당하고 있다. 자바와 WSDL의 매핑 및 자바 타입과 WSDL에서 참조하는 메시지의 스키마 타입으로의 매핑을 담당하고 실제 웹 서비스 메시지를 처리하는 런타임 프로세스 중에 필요한 정보들을 제공한다. JAX-WS 2.0에서 사용하게 되는 어노테이션의 정의는 웹 서비스에 국한된 어노테이션의 경우 Web Services Metadata(JSR-181) 명세와 JAX-WS 2.0 명세에 분산되어 정의되어 있다. 그 외에 메시지의 타입 매핑을 위해 범용적으로 사용되는 어노테이션은 JAXB 2.0 명세에 정의되어 있고, 리소스 인젝션과 생명 주기에 관련된 메소드들에 관련된 어노테이션들은 커먼 어노테이션(JSR-250)명세에 정의되어 있다. 여기서는 이러한 어노테이션들 중 웹 서비스 프로그래밍에 있어 자주 다루어 지는 것들을 위주로 좀 더 상세히 알아볼 것이다. 즉 웹 서비스 메타데이터 명세에 정의된 어노테이션이 소개 될 텐데, 참고로 JAX-WS 2.0과 JAXB 2.0 어노테이션은 웹 서비스 개발자가 직접 설정을 하는 것이 아니라 JAX-WS 툴에 의해서 생성된 코드에 자동으로 삽입이 된다.

웹 서비스 메타데이터(JSR-181) 명세에 정의된 어노테이션

원래 웹 서비스 메타데이터 명세는 자바EE 컨테이너 환경에서 자바 웹 서비스에 대한 정의를 쉽게 할 수 있도록 하기 위해 제안된 어노테이션을 정의하는 명세이다. 이 명세는 JAX-WS 명세가 정의되기 전에 이미 완성이 되어 있었으며, JAX-WS에서는 이 명세에 정의된 어노테이션을 활용하고 있다. 여기서 살펴 볼 어노테이션은 다음과 같다.

- javax.jws.WebService
- javax.jws.WebMethod
- javax.jws.OneWay
- javax.jws.WebParam
- javax.jws.WebResult
- javax.jws.soap.SOAPBinding

● javax.jws.WebService
@WebService 어노테이션은 어노테이션을 포함하고 있는 자바 빈 클래스가 웹 서비스를 구현한 엔드포인트 구현 빈 혹은 웹 서비스를 정의한 서비스 엔드포인트 인터페이스임을 명시할 때 사용한다. 이 어노테이션이 가질 수 있는 속성은 <표 1>과 같다.


속성 설명 타입 필수여부
name 웹 서비스의 이름. WSDL의 엘리먼트로 매핑됨.
기본값은 자바 클래스나 인터페이스의 패키지명을 제외한 이름.
String No
targetNamespace 웹 서비스로부터 생성된 XML 엘리먼트 와
WSDL에 사용될 XML 네임스페이스. 기본값은 JAX-RPC 스펙에 정의됨.
String No
serviceName 웹 서비스의 서비스 이름. WSDL파일의 < wsdl:service>엘리먼트로 매핑 됨.
기본값은 자바 클래스나 인터페이스의 패키지 명을 제외한 이름에 “Service” 라는 스트링 값을 추가함.
String No
wsdlLocation 미리 정의된 WSDL의 위치. wsdlLocation 값이 존재하면
SIB는 미리 정의된 WSDL 을 구현하고 있다는 것을 의미하며,
WSDL에 정의된 portType, binding은 서비스 구현 빈에 구현된 값들과 충돌이 없어야 한다.
String No
endpointInterface 존재하는 서비스 엔드포인트 인터페이스 의 패키지 명을
포함하는 클래스 이름으 로 이 속성은 클래스 경로에 엔드포인트 인터페이스 파일을 이미 생성하였을 때 설정할 수 있다.
String No
portName WSDL에 표시되는 wsdl:portName String No

<표 1> @WebService의 속성


● javax.jws.WebMethod
@WebMethod 어노테이션은 웹 서비스 오퍼레이션으로 공개하는 메소드를 설정할 때 사용한다. 이 어노테이션이 가질 수 있는 속성은 <표 2>를 참고하자.


속성 설명 타입 필수여부
operationName

주석이 사용된 자바 메소드가 wsdl상 에서 사용될 이름.
wsdl:operation의 name 속성값에 대응한다.
기본값은 자바 메소드의 이름이다.

String No
action 오퍼레이션에 적용될 “action” 속성. SOAPBinding의 경우,
SOAP메시지에서 SOAPAction 헤더의 값을 결정하게 된다.
기본값은 “ ”이다.
String No

<표 2> @WebMethod의 속성


● javax.jws.OneWay
@OneWay 어노테이션은 웹 서비스로 공개된 비즈니스 메소드가 반환 값이 존재하기 않고 단지 입력 파라미터만 존재할 때 사용한다. @WebMethod 어노테이션과 함께 사용된다. 웹 서비스 메소드의 반환 타입이 void가 아니거나 Holder 클래스 타입이 입력 웹 서비스 메소드의 파라미터의 타입으로 설정될 때, 웹 서비스 메소드가 필수처리예외(checked exception)을 던지는 경우에는 이 어노테이션을 붙일 수 없다. 이 어노테이션은 별도의 속성 정의가 필요하지 않다.

● javax.jws.WebParam
@WebParam 어노테이션은 웹 서비스 자바 메소드의 입력 파라미터와 WSDL 파일에서 파라미터를 표현 하는 XML 엘리먼트 간의 매핑을 설정하며 파라미터의 동작 특성도 설정할 수 있다. <표 3>과 같은 속성을 정의할 수 있다.


속성 설명 타입 필수여부
name 파라미터 이름. RPC스타일의 웹 서비스
에서는 파라미터를 나타내는 wsdl:part 엘리먼트에 대응이 되며, Document 스타일 웹 서비스에서는 파라미터를 나타 내는 XML 엘리먼트의 로컬 네임이 된다. 기본값은 메소드의 파라미터의 이름이다.
String No
targetNamespace 파라미터의 XML 네임스페이스.
파라미터가 XML 엘리먼트로 대응되는 Document 스타일의 웹서비스에서만 사용된다.
기본값은 웹 서비스의 targetNamespace값이다.
String No
mode 파라미터가 전달되는 방향. IN, OUT 혹은 INOUT중에 하나.
OUT과 INOUT 모드는 JAX-RPC에서 Holder 타입으로 정의되 는 파라미터들을 설정할 때만 적용가능 하다. 기본값은 IN이다.
enum No
header true로 설정되면 파라미터는 SOAP 메시 지 바디가 아니라
SOAP 메시지 헤더로 부터 가져오게 된다. 기본값은 false이다.
boo-lean No
partName RPC 혹은 DOCUMENT/BARE 스타일의 웹 서비스에서
파라미터에 해당하는 partName을 설정한다.
String No

<표 3> @WebParam의 속성


● javax.jws.WebResult
@WebResult 어노테이션은 웹 서비스로 공개되는 자바 메소드의 반환 값과 WSDL의 반환 값을 표현하는 XML 엘리먼트 간의 매핑을 설정한다. <표 4>와 같은 속성을 정의할 수 있다.


속성 설명 타입 필수여부
name WSDL에서 반환 값을 나타내는 엘리먼트 의 이름.
RPC스타일의 웹 서비스에서는 반환 값을 나타내는 wsdl:part 엘리먼트 에 대응이 되며,
Document 스타일 웹 서비스에서는 반환 값을 나타내는 XML 엘리먼트의 로컬 네임이 된다.
기본값은 RPC 및 DOCUMENT/WRA- PPED 스타일일 경우 “return”이며 DOCUMENT/BARE 스타일일 경우 메소드 이름+”Response” 이다.
String No
targetNamespace 파라미터의 XML 네임스페이스.
파라미터가 XML 엘리먼트로 대응되는 Document 스타일의 웹서비스에서만 사용된다.
기본값은 웹 서비스의 targetNamespace값이다.
String No
mode 반환값의 XML 네임스페이스.
반환값이 XML 엘리먼트로 대응되는 Document 스타일의 웹서비스에서만 사용된다.
기본값은 웹서비스의 targetNamespace 값이다.
String No
partName RPC 혹은 DOCUMENT/BARE 스타일 의 경우,
응답에 해당하는 partName을 설정한다. 기본값은 @WebResult.name 이다.
String No

<표 4> @WebResult의 속성


● javax.jws.soap.SOAPBinding
@SOAPBinding 어노테이션은 개발자가 웹 서비스의 SOAP 바인딩을 선택할 수 있게 해 준다. JAX-WS에서는 JAX-RPC에서 지원되었던 RPC/ENCODED 방식은 더 이상 지원하지 않는다. 즉 가능한 조합은 RPC/LITERAL, DOCUMENT/ LITERAL WRAPPED, DOCUMENT/LITERAL BARE위 세가지 타입이다. RPC/LITERAL 타입은 웹 서비스 메소드 이름에서 유추된 이름을 가지고 SOAP 요청/응답 메시지의 파라미터 부분을 한번 감싸게 된다. 이러한 메시지 형태의 생성은 SOAPBinding에 있어서의 하나의 약속이다. 그에 반해 DOCUMENT/LITERAL 방식은 WSDL에서 메시지의 형태를 스키마 타입으로 완전히 정의한다. 즉 SOAP 바디 내부의 모든 메시지는 WSDL내부의 스키마에 정의된 타입의 형태로만 만들어 질 수 있다. <표 5>는 @SOAPBinding의 속성을 나타낸다.

속성 설명 타입 필수여부
style 요청 및 응답 SOAP 메시지의 인코딩 스타일을 정의한다.
DOCUMENT 혹은 RPC 중의 하나이다.
enum No
use 요청 및 응답 SOAP 메시지의 포맷 스타일을 정의한다.
LITERAL 혹은 ENCODED 중 하나이다. 기본값은 LITERAL 이다.
enum No
parameterStyle 메소드 파라미터들이 SOAP 메시지 바디의 전체를 구성하는지(BARE), 혹은 파라미터들이
오퍼레이션 이름을 딴 최상위 엘리먼트의 안에 포함된 (WRAPPED) 엘리먼트인지를 결정한다. 기본값은 WRAPPED이다.
enum No

<표 5> @SOAPBinding의 속성


예제의 사용

이제까지 설명된 모든 샘플은 이미 설명한 대로 wsgen이나 wsimport와 같은 스크립트 툴로 필요한 산출물을 생성하고, 직접 WAR나 JAR 형태로 패키징 하여 동일하게 실행해 볼 수도 있다. 더불어 독자들의 편의를 위해 ant 툴로 실행 할 수 있도록 별도로 작성하여 첨부하였다. 이 예제들을 실행 시키려면 다음과 같이 웹 서비스 엔드포인트 종류에 따라 <리스트 12> <리스트 13><리스트 14>와 같이 실행하면 된다.
이제까지 JAX-WS 2.0 환경에서의 간단한 웹 서비스를 생성하고 호출하는 방법에 대해 살펴보았다. 여기에 포함된 예제와 설명은 가장 기본적인 내용을 다루고 있으며 이 외에도 JAX-WS 2.0에는 많은 특성들이 있다. 하지만 가장 전형적면서도 일반적인 웹 서비스 예제를 다루며 설명하였으니, 앞으로 JAX-WS 2.0을 이용한 프로그래밍의 기회가 있거나 이제 막 웹 서비스를 시작하려는 독자에게 도움이 되지 않았을까 한다.
몇 년 전부터 변화와 진화를 거듭해온 웹 서비스는 기술적인 면에서 이미 괄목할 만한 성장을 이루었지만 여전히 진행형이다. 또한 현재 광풍 처럼 밀어 닥치고 있는 SOA(Service Oriented Architecture)와 ESB(Enterprise Service Bus)의 연관 기술로도 큰 관심을 끌고 있다. 이는 표준에 기반한 웹 서비스라는 기술이 그 만큼 활용도가 넓다는 반증일 것이다. 앞으로도 더 큰 발전이 기대되고 있는 웹 서비스 분야에 JAX-WS 2.0이 좋은 진입로가 되어주길 희망하면서 이만 마무리 할까 한다.


<리스트 12>From Java 방식의 서블릿 엔드포인트 웹 서비스 예제 // 웹 서비스의 생성 ant gen-service-bottomup // Java EE 웹 서비스 클라이언트의 생성 ant gen-client package-client // Java SE 웹 서비스 클라이언트의 생성 및 실행 ant gen-client run-client


<리스트 13>From Java 방식의 EJB 엔드포인트 웹 서비스 예제 // 웹 서비스의 생성 ant gen-service-ejb // Java SE 웹 서비스 클라이언트의 생성 및 실행 ant gen-client-ejb run-client-ejb


<리스트 14>From WSDL 방식의 서블릿 엔드포인트 웹 서비스 예제 // 웹 서비스의 생성 ant gen-service-topdown // Java SE 웹 서비스 클라이언트의 생성 및 실행 ant gen-client run-client


참고자료
1. Java EE 5 안내서 - http://java.sun.com/javaee/5/docs/tutorial/doc/
2. Java EE 웹 서비스 소개 링크 - http://java.sun.com/javaee/technologies/webservices/
3. JSR 181(Web Services Metadata for the Java Platform) 명세 - http://www.jcp.org/en/jsr/detail?id=181
4. JSR 224(JAX-WS) 명세 - http://www.jcp.org/en/jsr/detail?id=224
5. JSR 222(JAXB) 명세 - http://www.jcp.org/en/jsr/detail?id=222
6. JSR 250(Common Annotations) 명세 - http://www.jcp.org/en/jsr/detail - id=250
7. Tmaxsoft 홈페이지 - http://www.tmax.co.kr
8. Java EE 5 SDK - http://java.sun.com./javaee
9. GlassFish 프로젝트 - https://glassfish.dev.java.net/
10. JAX-WS 프로젝트 - https://jax-ws.dev.java.net/