개발 생산성 향상을 위한
자바EE5의 어노테이션 활용
자바EE5에서는 기존에 XML에 기반을 둔 복잡한 프로그래밍 모델에서 탈피하여 좀 더 쉽고 직관적으로 애플리케이션을 개발할 수 있도록 메타데이터 어노테이션을 적극적으로 활용하기 시작하였다.
특집 2부에서는 자바EE5에서 어노테이션을 어떤 경우에 사용해야 하는지 알아본다.
자바EE5가 작년 자바원에서 공식 릴리즈 된지 벌써 1년이 지났다. 독자 여러분들도 잘 알고 있듯이 대부분의 자바 EE 기술은 선언적인 프로그래밍 모델(Declarative Programming Model)에 기반하고 있고 이는 이미 대세로 자리 잡은 듯하다.
자바EE5 이전에는 선언적인 프로그래밍 모델의 대부분을 XML 디스크립터를 통해서 구성했다. 반면에 자바EE5 이후부터는 메타데이터 어노테이션(metadata annotation)을 통해 좀 더 쉽고 직관적인 개발이 가능해 졌다.
하지만, 잘 살펴보면 자바EE5에서 도입된 어노테이션이 완전히 새로운 방식은 아니다. 이미 예전부터 많은 개발자들이 EJB 컴포넌트를 개발할 때 핵심 로직이 담긴 클래스 이외에도 복잡한 XML 디스크립터와 여러 인터페이스를 구현해야 하는 것에 대해 불만이 많았다. 따라서 이를 해결하고자 XDoclet과 같은 오픈 소스 프로젝트가 각광을 받았다.
XDoclet은 초창기의 메타데이터 어노테이션으로 볼 수 있는데 현재 자바 언어에서 제공하는 어노테이션과 형태가 매우 유사하다. 하지만, 소스 수준에서만 해당 정보가 유지되는 제약이 있어 코드 생성 툴을 위한 메타데이터로만 사용할 수 있었다. 현재 자바 언어의 어노테이션은 런타임에서 해당 클래스의 어노테이션을 읽어 올 수 있기 때문에 더 많은 분야에서 활용되고 있으며 대표적인 분야가 바로 자바EE 기술 분야이다.
어노테이션의 장점
자바EE5가 이전 버전에 비해 생산성이 얼마나 많이 향상되었는지 알아보기 위해 <표 1>을 살펴보자. <표 1>을 보면 굉장히 고무적이다. 이는 자바EE5에서 개선된 여러 가지 측면에서 설명할 수 있지만 어노테이션을 도입한 것이 가장 큰 영향을 주었다고 할 수 있다.
어노테이션이 이렇게 XML에 비해서 개발 생산성을 획기적으로 높여준다면, XML을 통해 메타데이터를 설정하는 것은 이제 구시대의 산물이며 더 이상 사용하지 말아야 하는 것일까? 이 질문에 대한 대답을 하려면 먼저 어노테이션과 XML이 가지는 장/단점을 비교해 보는 것이 좋을 것 같다. 먼저, 어노테이션이 각광을 받은 것은 분명 XML에 비해 아래와 같은 장점이 있기 때문이다.
● 코드의 가독성 증대 - 관련된 코드 곁에 메타데이터를 설정할 수 있으므로 해당 코드의 구조를 파악하기 위해 XML을 일일이 확인해야 할 필요가 없다.
● 개발 효율성 증대 - 복잡한 XML 스키마를 파악하지 않아도 되며, 다른 언어 구문과 마찬가지로 strong type 체크를 하므로 개발 툴과 컴파일러의 도움을 받을 수 있다.
● 프로그램 구조의 일관성 확보 - XML 설정은 의도적이든 의도적이지 않든지 간에 잘못 수정하거나 설정하면 원래 디자인 의도와 다른 방식으로 동작할 수 있다.
XML 설정에 기반을 둔 EJB 2.1 빈과 어노테이션만으로 작성된 EJB 3.0 빈을 비교해보면 앞서 말한 장점을 분명히 인식할 수 있을 것이다.
어노테이션의 단점
그러면, 반대로 어떤 점에 있어 어노테이션은 문제가 되거나 약점이 있을까? 하나씩 살펴보도록 하자.
1. 해당 클래스가 한 번 컴파일 되면 더 이상 메타데이터를 수정할 수 없다. 즉, 디플로이 되는 환경에 따라서 설정을 수정할 수 없다.
자바 EE에서는 개발된 애플리케이션을 실제 디플로이 하는 환경에 관계없이 이식성 있게 만드는 것을 중요하기 때문에, 디플로이 환경에 맞게 설정을 수정할 수 없다면 큰 문제가 아닐 수 없다. 보안 설정이나 데이터베이스 설정과 같은 것이 그런 예인데 이식성을 고려한다면 이런 설정은 XML에 담는 것이 적합하다.
2. 어노테이션은 모듈이나 애플리케이션 전반적인 메타데이터를 설정할 수 없다.
어노테이션은 범위가 커 봐야 클래스나 패키지 레벨로 한정하기 때문에 여러 클래스에 걸친 공통적인 설정이나 모듈레벨 설정이 어렵다. 예를 들어, EJB에서 모든 빈(Bean)에 대해서 공통 인터셉터를 달고 싶다거나, JPA에서 모든 엔티티 클래스에 대해서 공통적으로 DB의 어떤 스키마를 쓰게 하고 싶다면 이를 어노테이션으로 할 수가 없다. 따라서 이런 설정은 현재 XML을 통해서만 가능하다.
3. 어노테이션 처리 시 리플렉션을 통해 해당 클래스를 분석해야 하는 오버헤드가 있다.
이것은 디플로이 시간과 관련 있는데, 어노테이션을 쓰면 항상 느리다는 이야기는 아니다. XML도 실제로 파싱하고 처리하는 오버헤드가 있기 때문에 경우에 따라서 어노테이션 처리가 더 빠를 수 있다. 어노테이션 처리는 처리해야 하는 클래스의 개수에 비례한다. 때문에 만약 모듈 안에 클래스가 엄청나게 많고 어떤 클래스가 어노테이션을 가지고 있는지 알 수 없다면 모든 클래스를 스캔 해봐야 하므로 시간이 XML 처리에 비해 상대적으로 오래 걸리게 된다. EJB 3.0 모듈이나 JAX-WS 웹서비스, JPA 엔티티가 포함된 모듈의 경우가 이에 해당하므로 디플로이 시간이 한없이 느리다면 이런 점을 고려해야 한다.
4. 소스가 아닌 바이너리를 살펴보는 경우 어노테이션은 눈에 보이지 않기 때문에 해당 모듈의 구조를 파악하기가 어렵다.
예를 들어, EJB 2.1의 경우 해당 모듈의 XML 디스크립터를 열어보면 어떤 빈들이 있고, 어떤 타입인지 이런 정보를 외부에서 보기가 쉬웠다. 반면에 어노테이션만을 사용해서 만들어진 EJB 3.0의 경우는 클래스들 밖에 없기 때문에 이런 것들을 바로 파악할 수가 없다. 또한, 현재까지 나와 있는 디컴파일러또한 어노테이션을 보여주지 않기 때문에 바이너리만 가지고 구조를 파악하기가 쉽지 않다. 이를 위해서 클래스의 구조와 어노테이션을 파악해서 보여주는 툴이 있다면 편리할 것 같은데, 아직까지는 그런 툴을 나와 있지 않다.
XML과 어노테이션같이 쓰기
그러면 이제 앞서 제기한 질문에 답을 할 수 있는 시점이 된 것 같다. 어노테이션은 그 나름대로의 역할이 있지만 XML을 완전히 대체할 수는 없다. 따라서 자바EE5에서는 두 가지 형태의 메타데이터를 다 쓸 수 있도록 지원하고 있다. 이 경우 다음과 같이 3가지 조합이 가능하다.
1. 어노테이션만을 쓰는 경우
2. 어노테이션과 XML을 같이 쓰는 경우
3. 이전 방식처럼 XML만 사용하는 경우
어노테이션과 XML을 혼용하여 사용하는 경우에 XML은 어노테이션을 보충하거나 override하는 역할을 맡게 된다. 자바EE5의 모든 어노테이션은 그에 해당하는 XML 설정이 존재하는데 이런 경우에 우선순위는 항상 XML에게 있다. 여기서 고민이 시작된다. 그러면 어떤 경우에 어노테이션을 써야 하고 어떤 경우에 XML을 써야 할까? 3가지 경우가 다 쓰임새가 있기 때문에 아쉽게도 정답은 없고 경우에 따라서 선택을 해야 한다.
먼저, 어노테이션만 사용하는 경우는 간단한 애플리케이션을 작성하거나 빨리 애플리케이션을 작성하고자 할 때 유용할 것이다. 어노테이션은 XML에 비해 사용하기가 쉽기 때문에, 짧은 시간에 애플리케이션을 개발할 수 있다는 장점이 있다.
XML만 사용하는 경우는 이미 기존 애플리케이션이 J2EE 1.4 기반으로 작성되어 있거나, 해당 애플리케이션에 클래스가 수없이 많아 디플로이 시간이 오래 걸린다거나 전체 애플리케이션 구성을 소스가 아닌 바이너리와 XML을 통해 파악해야 할 필요성이 있는 경우에 유용할 수 있다. 이런 경우를 위해서 자바EE5 디스크립터에는 <리스트 1>과 같이 metadata-complete라는 속성을 true 값으로 세팅할 수 있다. 이렇게 설정하면 어노테이션 처리를 전혀 하지 않고 XML만 처리하게 된다.
<리스트 1> metadata-complete 속성
xmlns="http://java.sun.com/xml/ns/javaee" ...>
...
metadata-complete 속성의 기본 값은 false이기 때문에 보통은 어노테이션에 대한 처리가 수행된다. 한 가지 주의할 점이 있다면, EJB 2.1과 같이 기존 J2EE 1.4 모듈을 사용할 경우에는 어노테이션 처리가 필요 없기 때문에 이를 처리하지 않는 것이 일반적이다. 하지만 이를 구분하기 위해 WAS에 따라서 디스크립터에 있는 version attribute 값을 보게 된다. 따라서 <리스트 2>와 같이 어노테이션을 사용해 놓고 디스크립터에 version을 자바EE5 이전 버전으로 해놓으면 어노테이션 처리가 되지 않을 수 있다(제품마다 다를 수 있으며 JEUS 6의 경우 처리하지 않는다).
<리스트 2> EJB 3.0 빈에 2.1용 ejb-jar.xml를 사용하는 경우
xmlns="http://java.sun.com/xml/ns/j2ee" ...>
...
마지막으로, 어노테이션과 XML을 같이 사용하는 경우다. 아마 대부분의 일반적인 애플리케이션에 적합한 방법일 것이다. 애플리케이션 상에서 디자인 타임에 결정되는 부분에 대해서는 어노테이션을 사용하는 것이 좋고, 실제 디플로이 환경에 따라 바뀔 수 있는 부분의 경우 XML을 사용하여 표기하는 것이 좋다. 예를 들어, EJB와 같은 경우 해당 빈이 Stateless 세션 빈인지 Stateful 세션 빈인지는 해당 클래스 디자인 타임에 결정된다. 비즈니스 메소드가 CMT(Container-Managed Transaction)를 사용할 지 BMT(Bean-Managed Transaction)를 사용할지도 디자인 타임에 결정된다. 따라서 이런 부분은 어노테이션으로 표기하는 것이 적합하다. 하지만 해당 빈이 실제로 어떤 JNDI 이름으로 바인딩 될 것인지, 그리고 메소드에 어떤 보안 정책을 부여할 것인지는 디플로이 환경에 따라 달라질 수 있기 때문에 이는 XML 설정으로 표기하는 것이 적합하다. JPA의 경우도 해당 엔티티 클래스의 어떤 필드가 DB로 매핑 될 것인지나 어떤 JPQL(Java Persistence Query Language)을 사용하는가 역시 디자인 타임에 결정되는 문제이므로 어노테이션으로 표기한다. 실제 DB 테이블의 이름이나 칼럼 이름 그리고 혹시 DB에 특화된 SQL을 사용하는 경우에는 이를 XML에 등록하여 코드 수정 없이 변경할 수 있게 하는 것이 이식성 높은 애플리케이션을 작성하는 방법이다.
제품에 특화된(vendor-specific) 어노테이션
자바EE5 표준 스펙이 정의된 어노테이션들은 그 범위가 표준 디스크립터에서 정의할 수 있는 표준 설정에만 한정되어 있다. 알다시피 각 WAS는 제품 특성 별로 고유의 XML 디스크립터 설정이 있다. 그러면 이런 제품별 확장(extensions) 설정은 어노테이션을 사용할 수 있을까? 이건 제품에 따라 다르다. WebLogic Server 10은 이런 확장 어노테이션을 전혀 지원하지 않기 때문에 여전히 설정을 위해 XML 디스크립터가 필요하다. 제우스 6이나 JBoss의 경우 몇 가지 설정에 대해서(예를 들어, @Clustered, @Idempotent와 같은 클러스터링 설정) 어노테이션을 사용할 수 있다. 하지만, 모든 설정을 어노테이션으로 할 수 있도록 지원하지는 않기 때문에 세밀한 설정을 하는 경우에는 XML 디스크립터가 여전히 필요할 것이다. JPA 프로바이더 중에도 하이버네이트나 톱링크(11g 부터)의 경우 확장 어노테이션을 지원하여 표준 스펙 이외의 확장된 ORM 기능을 사용할 수가 있다. vendor-specific 어노테이션의 경우 해당 어노테이션 클래스가 없어도 JVM에서는 이를 무시하기 때문에 특정 제품의 어노테이션을 썼다고 해서 다른 제품에서 돌릴 때 ClassNot FoundException과 같은 에러는 나지 않는다. 결국 특정 제품에 종속(Lock-in) 되는 문제는 별로 없다고 볼 수 있다.
지금은 아니지만, 앞으로 각 제품들이 점점 어노테이션을 더 많이 지원할 것이라고 생각된다. 이에 따라 XML 설정에 대한 필요성은 많이 줄어들 것이다.
자바 EE 6에서의 어노테이션 활용
현재 논의되고 있는 자바 EE 6에는 Web Beans와 EJB 3.1, 자바 퍼시스턴스(Java Persistence) 2.0과 같은 비즈니스 로직 레이어와 Servlet 3.0이나 JSF 2.0같은 프리젠테이션 레이어 기술들이 포함될 것으로 보인다. 지금 부터는 이 중에서 현재 어느 정도 진행되고 있는 엔티티Web Beans와 EJB 3.1 기술에 대해서 살펴보고 어떤 식으로 어노테이션을 활용하는지 살펴보자.
엔티티Web Beans(JSR 299)는 JBoss Seam을 만든 개빈 킹(Gavin King, 하이버네이트 개발자로도 유명한)이 시작한 것으로 JSF와 EJB 3.0 컴포넌트 기술을 연결하는 스펙이다. 기본적으로 JBoss Seam의 컴포넌트 기술을 표준화하기 위해 시작하였지만, 현재는 구글 Guice(주이스 혹은 쥬스로 발음한다) 진영의 참여로 좀 더 일반적인 컴포넌트 기술로 논의가 진행 되고 있다. 엔티티Web Beans를 살펴보기 위해서는 이 기술의 기본 배경인 Seam 기술에 대해 먼저 이해할 필요가 있을 듯하다.
JBoss Seam
Seam은 JSF가 비즈니스 로직을 담당하는 부분을 POJO(Pl ain-Old Java Object) Managed Bean(혹은 Backing Bean)으로 처리한다. 결국 DB와 같은 트랜잭션한 리소스는 EJB 빈으로 처리하는 것에 착안하여 중복된 코드를 줄이고 EJB 빈을 아예 JSF의 Managed Bean 역할로 활용하고자 하는 아이디어에서 출발하였다. 사실 이제 EJB 빈은 JSF의 Managed Bean처럼 POJO 형태로 만들 수 있으므로 구현상 별 차이가 없다. 또, CMT(Container-Managed Transaction)를 지원하기 때문에 DB를 다루는 트랜잭션 코드를 쉽게 작성할 수가 있으며, JSF와 달리 어노테이션 중심으로 빈을 작성할 수 있다. 인터셉터와 같은 AOP 개념을 적용할 수도 있기 때문에 여러 가지 측면에서 활용가능성이 더 높다. 따라서 Seam은 EJB Stateless 세션 빈과 Stateful 세션 빈 그리고 JPA 엔티티 빈을 바로 JSF Managed Bean으로 사용할 수 있는 기능을 제공한다. 그래서 데이터에 해당하는 부분은 POJO 엔티티 빈으로 세팅해 놓고 있다가 어떤 액션이 들어오면 세션 빈을 통해 DB와 동기화하는 것이 가능하게 된다.
또한, Seam은 전통적인 웹 애플리케이션의 멀티-윈도우(Multi-window) 문제를 해결하기 위해 컨텍스트에 기반한 프로그래밍 모델(Contextual Programming Model)에 기반하고 있다. 예를 들어, 어떤 사용자가 쇼핑몰에서 주문을 하는데 여러 윈도우를 열어놓고 동시에 여러 상품을 주문하는 상황을 생각해보자. 주문이 여러 페이지에 걸쳐 있다고 한다면 보통은 각 주문에 관련된 중간 데이터(상품이나 신용카드 정보 등)를 세션(HttpSession)에 저장할 것이다. 문제는 여러 윈도우는 같은 세션을 사용하기 때문에 동시에 주문이 일어나면 데이터가 서로 엉켜서 주문이 이상하게 될 수가 있다는 데 있다. 이런 문제를 해결하려면 각 주문은 독립적인 conversation으로 다루어야 한다. 이런 상황은 쇼핑몰이 아니더라도 어떤 웹 애플리케이션에서도 비슷하게 일어날 수 있는 시나리오이다. Seam에서는 세션 scope보다 더 작은 개념의 conversation scope를 제공하며 이러한 컨텍스트 scope를 관리해 준다.
또한, Seam은 자바EE5와 많은 IoC 컨테이너에서 제공하고 있는 인젝션(Dependency Injection) 보다 좀 더 확장된 동적 바이젝션(Bijection)이라는 특징을 제공한다. 바이젝션은 인젝션(Injection)과 아웃젝션(Outjection)을 포괄한 말로 양방향성 특성이 있다. 이는 현재 컨텍스트에서 빈을 호출하기 전에 변수에 인젝션을 해주고, 호출 후에는 해당 변수를 다시 컨텍스트에 다시 바인딩 해주게 되어 좀 더 단순하게 컨텍스트에 기반한 프로그램을 할 수 있도록 해준다.
Seam에 대한 이해를 좀 더 높이기 위한 예제로 <리스트 3>을 살펴보자.
<리스트 3> Seam 빈 클래스 예제
@Stateful
@Name("hotelBooking") .................................(1)
@Restrict("#{identity.loggedIn}")
public class HotelBookingAction implements HotelBooking {
@PersistenceContext(type=EXTENDED)
private EntityManager em;
@In ............................................... (2)
private User user;
@In(required=false) @Out .......................... (3)
private Hotel hotel;
@In(required=false)
@Out(required=false)
private Booking booking;
...
@Begin .............................................(4)
public void selectHotel(Hotel selectedHotel) {
hotel = em.merge(selectedHotel);
}
public void bookHotel() {
booking = new Booking(hotel, user);
Calendar calendar = Calendar.getInstance();
booking.setCheckinDate( calendar.getTime() );
calendar.add(Calendar.DAY_OF_MONTH, 1);
booking.setCheckoutDate( calendar.getTime() );
}
public void setBookingDetails() { ... }
public boolean isBookingValid() {
return bookingValid;
}
@End ...............................................(5)
public void confirm() {
em.persist(booking);
facesMessages.add("Thank you, #{user.name}, your confimation number for #{hotel.name} is #{booking.id}");
log.info("New booking: #{booking.id} for #{user.username}");
events.raiseTransactionSuccessEvent("bookingConfirmed");
}
@End
public void cancel() {}
@Destroy @Remove
public void destroy() {}
}
<리스트 3>은 Seam에 포함되어 있는 호텔 예약 예제 프로그램의 일부이다. HotelBookingAction 클래스는 EJB Stateful 세션 빈인데, 바로 conversation 상태를 관리하는 Seam 빈으로 사용할 수 있다. Seam은 EJB 3.0에서 정의한 어노테이션 이외에 Seam의 빈으로 활용하기 위해서 여러 어노테이션을 정의해 놓았다. (1)의 @Name은 해당 컴포넌트를 Seam의 빈으로 명시하면서 고유 이름을 할당하기 위한 것이다. 이 이름을 이용하여 JSF 페이지에서 EL을 통해 다음과 같이 빈에 접근할 수 있다.
(2)의 @In은 동적 인젝션을 통해 다른 컴포넌트를 세션 컨텍스트에서 얻어오는 것이다. 여기서 User, Hotel, Booking은 JPA 엔티티 빈으로 DB에 해당하는 객체 도메인 모델을 나타낸다. Seam에서는 엔티티 빈을 Seam 빈으로 바로 쓸 수 있기 때문에 별도의 빈을 만들 필요가 없어졌다. 각각 ‘user’, ‘hotel’, ‘booking’이라는 이름으로 현재 컨텍스트에서 얻어와 해당 필드에 인젝션을 해주게 되므로 이를 얻어오기 위해 별도의 코딩을 할 필요가 없다. hotel과 booking 필드는 (3)의 @Out이라는 아웃젝션 어노테이션도 가지고 있는데 이는 해당 필드를 메소드 호출 후에 다시 현재 conversation 컨텍스트에 바인딩 하게 된다는 것을 의미한다. 이런 식으로 빈은 좀 더 컨텍스트 중심적인 프로그래밍을 할 수가 있으면서 바이젝션을 덕택으로 컨텍스트에서 값을 얻어오거나 세팅하는 코드를 줄여준다. (4)의 select Hotel() 메소드는 @Begin을 가지고 있는데, 이는 새로운 conv ersation을 시작시킨다. 반대로 (5)의 @End는 해당 conversa tion을 종료시킨다. conversation은 별도의 타임아웃 값을 가지고 있어서 어느 정도 시간이 지나면 자동으로 삭제되기 때문에 브라우저를 그냥 닫거나 하는 등의 비정상적인 종료가 있더라도 메모리를 계속 증가시키지는 않는다.
물론 Seam은 일반 JavaBean 형태도 지원하기 때문에 위 빈을 반드시 Stateful 세션 빈으로 만들지 않아도 된다. 하지만, EJB는 컨테이너에서 트랜잭션을 관리해 주는 기능(CMT)이 있기 때문에 별도의 트랜잭션 코드 없이도 DB와 동기화를 위한 JPA 코드를 쉽게 구현할 수 있게 된다. 만약 EJB가 아니었다면 해당 빈은 로직과 관계없는 많은 트랜잭션 코드를 가지고 있었을 것이다. 이런 이유로 Seam에서는 stateful 세션 빈과 엔티티 빈을 많이 사용하게 된다.
Seam에 대해서는 이 정도로 다루기로 하고 좀 더 자세한 내용을 알고 싶은 독자는 Seam 튜토리얼을 살펴보길 바란다.
Web Beans
Web Beans는 Seam의 컴포넌트 모델에 기반을 두고 있지만 구글 Guice의 장점을 많이 수용하고 있다. 즉, Guice처럼 어노테이션을 사용하여 다양한 scope을 정의할 수 있고 하나의 인터페이스에 대해 여러 개의 구현 클래스를 가질 수도 있어서 상황에 맞게 인젝션 해주는 등의 유사한 특징을 가지고 있다(Web Beans JSR에 Guice 개발자가 주도적으로 참여하고 있음). 참고로 JSR 299는 아직 Early Draft 이전단계로 여기서 언급한 내용은 얼마든지 추후에 바뀔 수 있다(좋은 의견이 있다면 언제든지 필자에게 피드백을 주면 스펙 논의 상에 충분히 고려하겠다).
Web Beans는 Seam처럼 어노테이션을 통해 컴포넌트를 정의하게 된다. 예를 들어 <리스트 4>와 같이 일반 JavaBean이나 EJB 빈을 @Component를 통해 컴포넌트로 지정할 수 있다.
<리스트 4> Web Bean 컴포넌트
@Component
public class PaymentProcessorImpl implements PaymentProcessor { ... }
@Stateless
@Component
@Named("loginAction")
public class LoginActionImpl implements LoginAction { ... }
컴포넌트는 해당 컴포넌트에 접근하기 위한 인터페이스를 가지는데 이를 통해 실제 사용될 컴포넌트 클래스를 결정하게 된다. 인젝션은 다음과 같이 수행하며 해당 인터페이스를 구현한 컴포넌트 클래스의 인스턴스가 인젝션 된다(자바EE5의 인젝션과 유사하지만 앞서 말한 Seam과 같이 메소드 호출 전에 동적으로 인젝션이 이루어진다).
@In PaymentProcessor paymentProcessor;
Seam과 달리 Web Beans는 하나의 인터페이스를 구현한 여러 개의 컴포넌트 구현 클래스를 지원하기 위해 바인딩 어노테이션(Binding Annotation)이라는 Guice의 개념을 도입하였다. 즉, 다음과 같이 @BindingType으로 어노테이션을 정의할 수 있는데 이를 컴포넌트와 인젝션 되는 필드에 사용할 수 있다.
@BindingType
@Retention(RUNTIME)
@Target({TYPE, FIELD, METHOD})
public @interface Synchronous {}
정의된 어노테이션을 다음과 같이 실제 컴포넌트에 바인딩 어노테이션으로 지정하게 된다.
@Synchronous @Component
public class SynchronousPaymentProcessorImpl implements PaymentProcessor { ... }
그러면, 결국 다음과 같이 바인딩 어노테이션을 사용하여 원하는 타입의 컴포넌트를 인젝션할 수가 있게 된다.
@In @Synchronous PaymentProcessor paymentProcessor;
@In @Asynchronous PaymentProcessor paymentProcessor;
앞서 @Named(“loginAction”)를 사용하여 컴포넌트를 정의하였는데 이는 특수한 형태의 바인딩 어노테이션이다. 주로 다음과 같이 타입 기반이 아닌 EL과 같은 이름 기반의 환경에서 컴포넌트를 참고할 때 사용된다.
Seam에서는 항상 컴포넌트가 이름을 통해서만 접근할 수 있었다(EL이든 자바 클래스에서든). Web Beans에서는 이 뿐만 아니라 어노테이션을 활용하여 Guice와 마찬가지로 타입 기반 인젝션도 지원하고 동일한 인터페이스의 여러 구현 클래스를 지원하는 등 좀 더 유연한 컴포넌트 사용 환경을 제공한다.
Seam과 다른 Web Beans의 또 다른 특징은 어노테이션을 사용하여 사용자 정의가 가능한 custom scope를 지원한다는 것이다. 바인딩어노테이션이 어떤 컴포넌트 구현 클래스를 사용할 지를 결정하는 매커니즘이라면 scope은 해당 클래스의 인스턴스를 어떤 범위에서 공유할 것인가를 결정하는 메커니즘과 관련되어 있다. 이미 앞에서 Seam에서는 컨텍스트에 기반한 프로그래밍 모델을 위해서 Conversation scope를 포함하여 다양한 scope을 지원한다고 했다. Web Beans 역시 기본적으로 no scope와 request, session, conversation, application(singleton) scope을 지원한다. 또한 사용자 정의가 가능한 custom scope을 지원하기 때문에 복잡한 애플리케이션이나 새로운 프레임워크에 확장성을 제공한다. 예를 들어, BPM과 같은 영역에서는 하나의 process에 해당 하는 오래 지속되는 scope을 정의할 수 있다. 또, 트랜잭션 기반 애플리케이션에서는 하나의 트랜잭션에 바인딩 되는 transaction scope과 같은 것도 정의하여 사용할 수 있을 것이다.
예를 들어, 다음과 같이 이미 정의된 conversationscope을 사용하면 별도의 코드 없이 해당 scope 안에서 하나의 인스턴스만 유지하여 conversation 상태를 관리하게 된다.
@Component @ConversationScoped
public class PaymentProcessorImpl implements Payment Processor { ... }
혹은, 다음과 같이 custom scope을 정의할 수도 있다. 이때는 Context API를 사용하여 직접 scope을 시작/종료 하게 된다(아마 Web Beans 기반 제 3의 프레임워크에 유용한 기능이 될 듯하다).
@ScopeType ...
public @interface TransactionScoped {}
@Component @TransactionScoped
public class TransactionScopeProcessorImpl implements Processor { ... }
지금까지 살펴본 Web Beans는 현재 초기단계이며 앞으로 계속 흥미로운 내용들이 추가될 것이다. 이는 따로 따로 발전하던 웹 기술과 EJB 기술의 대통합(?)을 위한 밑거름을 제공한다는 점에서 주목할 만한 중요 기술이 될 것으로 예상된다.
EJB 3.1
EJB 3.0에서는 개발의 편의성을 향상시키기 위해서 어노테이션을 도입하는 등 많은 개선이 있었다. EJB 3.1은 이에 더해서 웹 환경에서 EJB를 사용하기 쉽게 하자는 등 다음과 같은 개선안을 가지고 준비 중이다.
● 비즈니스 인터페이스 없는 로컬 빈
● WAR 안에 EJB를 패키징하는 기능
● 싱글턴(singleton) 빈
● 빈이 컨커런시를 관리하는 기능(Bean-managed concurrency) 등의 여러 가지 컨커런시 옵션
● 크론(cron) 스타일의 타이머 서비스
● 비동기(Asynchronous) 메소드 지원
앞의 세 가지 개선사항은 Seam과 같은 웹 기반 컴포넌트 기술에서 EJB를 사용하면서 필요성이 증대되었다. 사실, 웹 애플리케이션의 경우 주로 EL을 통해 빈을 접근하므로 별도의 인터페이스가 필요하지 않다. 때문에 EJB 빈도 이에 맞추어 별도의 인터페이스를 만들 필요가 없다면 좋을 것이다(이미 JPA 엔티티 빈은 인터페이스가 필요 없다). 또한, Seam이나 Web Beans와 같은 웹 애플리케이션 환경에서 EJB 빈을 사용하는 경우 굳이 EJB를 별도의 모듈로 패키징하고 이를 다시 EAR로 패키징하는 것은 또 하나의 불필요한 복잡한 과정이다. 대신에 이를 WAR 안에 같이 넣을 수 있다면 패키징이 좀 더 단순해 질 것이다. 또한, 싱글턴 빈은 싱글턴 scope(Web Beans에서는 application scope)를 가지는 빈을 필요로 하는 경우에 유용한 기능이 될 수 있을 것이다. 이런 경우에 굳이 기존 EJB 빈처럼 풀링을 할 필요가 없다. 싱글턴 빈은 동시에 여러 클라이언트가 접근할 경우 컨커런시 컨트롤을 어떻게 할 것이냐는 문제가 있다.
이를 위해서 <리스트 5>와 같이 @ReadOnly, @ReadWrite와 같은 컨커런시 어노테이션을 정의하여 컨테이너가 Read/Write 락(lock)을 통해 컨커런시를 컨트롤 해주도록 한다.
<리스트 5> 싱글턴 빈
@Singleton
public class SingletonBean {
private SharedApplicationData data;
@ReadOnly
public int getFoo() {
return data.foo;
}
@ReadWrite
public void update() {
// update shared data...
}
}
또한, 컨테이너에게 굳이 컨커런시 컨트롤을 맡기고 싶지 않은 경우를 위해 @BeanManagedConcurrency와 어노테이션을 통해 이를 설정할 수 있는 기능도 제공한다.
재미있는 것은 다양한 비동기 방식을 지원하기 위한 개선안도 있다는 사실이다. EJB는 기존부터 타이머 서비스를 지원하여 Statelsss 세션 빈이나 MDB의 경우 일정 시간마다 메소드가 호출되게 할 수 있었다. 하지만 이는 디플로이가 되면 시작되는 것이 아니라 어떤 API를 통해 직접 시작시켜야 하는 불편함이 있어서 크론(cron) 스타일의 주기적인 타이머 작업을 수행시키기에는 부족하였다. 이를 <리스트 6>처럼 크론 작업과 같이 선언적으로 정의하여 타이머 작업을 수행시킬 수 있는 기능이 고려되고 있다.
<리스트 6> 크론 스타일 타이머 빈
@Stateless @EJBTimer("0 12 1 * *") // 매달 첫째날 정오에 수행
public class AccountBean {
@Timeout void sendMontlyStatements(Timer t) {
// ...
}
}
또 다른 방식으로 <리스트 7>와 같이 메소드 호출 자체를 비동기 호출이 되도록 하는 기능도 고려되고 있다.
<리스트 7> 비동기 메소드를 가지는 빈
@Stateful @BeanManagedConcurrency
public class AsyncTaskBean implements AsyncTask {
private boolean taskComplete = false;
@Asynchronous public void doSomething(Details d) {
...
taskComplete = true;
}
public boolean isTaskComplete() {
return taskComplete;
}
}
JSR 303 Beans Validation
어노테이션을 이용한 또 하나의 재미있는 응용은 JSR 303에서 논의되고 있는 validation 메타데이터다. JSR 303 Beans Validation 스펙은 <리스트 8>에서 보듯이 자바 기본 타입에 어떤 제약사항(constraint)을 가하고 싶을 때 사용할 수 있는 공통 validation 메타데이터를 정의한다. 이미, 하이버네이트에서는 이런 형태의 어노테이션을 사용할 수가 있었는데(Hibernate Validator) 이를 좀 더 표준화 하여 일반적인 JavaBean과 JPA와 같은 곳에서 사용할 수 있도록 하는 것이 JSR 303이 추구하는 방향이다. JSR 303이 완료되면 JPA 2.0에서 이를 채택할 것으로 보인다.
<리스트 8> Bean Validation을 적용한 엔티티 빈
@Entity
public class Employee {
@Id @GeneratedValue
protected Integer empId;
@Required protected String name;
@Length(max=5)protected String locationCode;
@Max(240)protected Integer vacationAccrued;
...
}
만약 <리스트 8>에서 제약사항을 XML 메타데이터로 정의하고자 했다면 굉장히 불편하고 코드의 가독성도 떨어졌을 것이다. 어노테이션은 이런 측면에서 굉장히 편리하다.
지금까지 자바EE5에서 도입된 어노테이션이 가지는 장/단점과 XML과의 관계, 그리고 자바 EE 6에서 논의되고 있는 여러 어노테이션 기술에 대해서 살펴보았다.
어노테이션은 기존 XML 메타데이터에 비해서 코드의 가독성, 사용상 편리성 및 효율성 등에서 장점이 있기 때문에 앞으로 많은 차기 자바 EE 기술에서 앞다투어 채택될 것으로 보인다. 앞서 살펴본 Web Beans와 같은 컴포넌트 모델을 XML로 구현한다면 굉장히 복잡해졌을 텐데 어노테이션을 통해 놀라울 정도로 쉽게 모델을 정의하고 하고 있다. 또한, Beans Validation과 같은 기술은 자바의 타입을 좀 더 상세하게 정의할 수 있는 중요한 기반을 제공하고 있다. 이렇게 어노테이션은 단순히 XML을 대체하는 메타데이터가 아니라 자바 기술에 있어서 새로운 도약을 의미하는 중요한 요소로 점점 각광을 받고 있다.
하지만 어노테이션은 XML을 완전히 대체하는 메타데이터는 아니므로 여전히 XML은 꼭 배워야 하는 항목에서 빠지지는 않을 것으로 보인다. 오히려 훌륭한 개발자라면 어느 경우에 어노테이션을 쓰고, 어느 경우에 XML을 써야 하는지 적절하게 판단을 하여 사용하는 능력이 필요할 것이다. 독자 여러분들도 어노테이션을 적극적으로 도입하고 시도하여 다양한 활용가능성을 점쳐보는 것도 재미있을 것이다.
댓글 없음:
댓글 쓰기