-샘플 아카이브 다운로드-
EJB 인터셉터 Part 1
EJB 인터셉터 Part 2
글쓴이: Mahesh Kannan
EJB 3.0 http://jcp.org/en/jsr/detail?id=220 스펙에 소개된 새로운 기능 중 하나로 인터셉터(Interceptor)를 꼽을 수 있는데, 이는 사용자가 엔터프라이즈 빈의 호출 흐름에 삽입할 수 있는 메소드이다. 사용자는 엔터프라이즈 빈의 비즈니스 메소드를 인터셉트하기 위한 인터셉터를 정의할 수 있으며, 이 경우 인터셉터 메소드는 빈의 비즈니스 메소드가 호출되기 전에 실행된다. 또는, 엔터프라이즈 빈의 라이프사이클 이벤트를 인터셉트하기 위한 인터셉터를 정의할 수도 있다. 이 경우 인터셉터 메소드는 빈의 라이프사이클 이벤트를 위한 콜백 메소드로 실행된다.
본질적으로 인터셉터는 메소드 코드를 수정하지 않고도 비즈니스 메소드에 기능을 추가할 수 있도록 하는 방법을 제공한다. 예를 들어, 사용자는 인터셉터를 이용하여 매개변수를 비즈니스 메소드에 건네주기 전에 이를 검증하거나 비즈니스 메소드가 호출되는 시점에서 보안 점검을 수행할 수 있다. 인터셉터는 또한 애플리케이션 내의 복수 컴포넌트에 걸쳐 관여하는(때로는 이를 "crosscutting" 작업이라고 부르기도 한다) 로깅이나 프로파일링 같은 액션에도 유용하며, 인터셉터들을 체인으로 묶을 수 있는 유연성도 제공한다. 체인 내의 각 인터셉터는 비즈니스 메소드가 호출되기 전에 또는 라이프사이클 이벤트에 응답하여 특정 연산을 수행할 수 있다.
본 테크 팁에서는 인터셉터를 이용하여 엔터프라이즈 빈을 위한 비즈니스 메소드에 관한 프로파일링 정보를 수집하는 과정을 예시해보도록 한다. 본 팁에는 예제 패키지 http://java.sun.com/mailers/techtips/en ··· rcpt.jar 가 첨부되어 있으며, 팁의 코드 예제들은 패키지에 포함되어 있는 예제 소스 코드에서 발췌한 것이다. 예제에는 Java EE 5 SDK를 사용했으며, Java EE 5 SDK는 Java EE 다운로드 페이지 http://java.sun.com/javaee/downloads/index.jsp에서 다운로드할 수 있다.
예제 빈
인터셉터 예시를 위해 먼저 String 리버스를 위한 메소드를 제공하는 간단한 stateless 세션 빈을 사용해보기로 하자. 빈은 StringService라 불리는 비즈니스 인터페이스를 구현한다.
StringService의 형태는 텍스트 버전에서 볼수 있다. http://java.sun.com/mailers/techtips/en ··· ct06.txt
인터셉터 정의하기
앞에서 얘기한 것처럼, 사용자는 인터셉터 메소드를 이용하여 비즈니스 메소드나 라이프사이클 이벤트를 인터셉트할 수 있다. 비즈니스 메소드를 인터셉트하는 인터셉터를 통상 AroundInvoke 메소드라고 하는데, 그 이유는 메소드에 @AroundInvoke 주석을 첨부하여 정의할 수 있기 때문이다.
사용자는 엔터프라이즈 빈 자체 또는 외부 클래스(이후로 본 팁에서는 인터셉터라고 부르기로 한다) 상에서 AroundInvoke 메소드를 정의할 수 있다. 인터셉터 클래스는 자바 플랫폼의 다른 클래스와 마찬가지로, 특수 클래스를 확장하거나 인터페이스를 구현할 필요가 없다.
AroundInvoke 메소드 작성하기
사용자는 단순히 메소드에 @AroundInvoke 주석을 첨부함으로써 AroundInvoke 메소드를 정의할 수 있다(또는 빈의 배치 서술자 내에 있는 요소를 통해 정의할 수도 있다). AroundInvoke 메소드는 반드시 다음 요구사항을 충족해야 한다.
각 클래스에 대해 하나의 AroundInvoke 메소드만 허용된다.
이 때, 반드시 no-arg public 생성자가 포함되어야 하며,
javax.interceptor.InvocationContext 객체를 인자로 하여 java.lang.Object 객체를 반환해야 한다.
이 메소드는 비즈니스 인터페이스에 지정된 애플리케이션 예외 또는 런타임 예외를 throw할 수 있으며, private, package private, protected, public 등으로 선언될 수 있다.
이 메소드는 InvocationContext.proceed()를 호출함으로써 지속적인 호출 의도를 알려야 한다.
인터셉트된 비즈니스 메소드의 실행에 소요된 시간을 출력하는 인터셉터 클래스를 텍스트버전에서 볼수 있다
인터셉터 메소드는 앞서 열거한 요구사항을 충족한다는 점에 유의할 것.
이 보기의 인터셉터 메소드는 별도의 클래스에서 정의되지만, 단순히 빈 클래스 자체에 추가될 수도 있다. 하지만, 메소드 프로파일링은 StringService의 기본 목적과는 아무런 관련이 없기 때문에 인터셉터를 별도의 클래스로 유지하는 것이 보다 합리적이다.
빈에 인터셉터 추가하기
MethodProfiler 인터셉터가 정의되었다면 이를 StringAccountServiceBean에 추가해보기로 하자. 이를 위해서는 빈 내의 @Interceptors 주석을 이용해야 하는데, 이 주석은 인터셉터 클래스를 지정한다.
이제 StringServiceBean 상에서 reverse 메소드가 호출되면, AroundInvoke 인터셉터 메소드 프로파일이 reverse 메소드를 완료하는 데 소요된 시간을 출력한다.
인터셉터 체인과 InvocationContext
사용자는 복수의 인터셉터를 체인으로 묶어서 빈의 메소드를 인터셉트할 수 있다. 빈과 연결되는 인터셉터들의 리스트를 식별하는 데는 @Interceptors 주석을 이용한다(또는 빈의 배치 서술자에서 체인을 지정할 수도 있다). 인터셉터 호출 순서는 @Interceptors 주석(또는 배치 서술자)에서 지정되는 순서와 동일하다.
런타임 시에는 비즈니스 메소드 호출 또는 라이프사이클 이벤트 발생 전에 컨테이너가 InvocationContext 객체의 인스턴스를 생성하여 각 인터셉터 메소드에 건네주고, InvocationContext 인터페이스는 비즈니스 메소드 이름이나 비즈니스 메소드에 건네지는 매개변수 같은 비즈니스 메소드에 관한 정보를 얻기 위한 일련의 메소드를 제공한다.
InvocationContext 내의 proceed() 메소드는 체인 내의 다음 인터셉터 메소드가 호출되도록 하며, 마지막 AroundInvoke 인터셉터 메소드가 호출된 후에는 proceed() 메소드가 빈의 비즈니스 메소드를 호출한다. 인터셉터 메소드는 항상 InvocationContext.proceed()를 호출해야 하는데, 만약 메소드가 호출되지 않으면 이어지는 인터셉터 메소드, 빈 비즈니스 메소드 또는 라이프사이클 콜백 메소드도 호출이 불가능해진다.
인터셉터 체이닝 이용하기
앞에서 복수의 인터셉터를 체인으로 묶어 빈의 메소드를 인터셉트하는 데 사용할 수 있다는 것을 배운 바 있다. 이제 이 기능을 이용하여 흥미로운 작업을 수행해보기로 하자.
StringServiceBean 내의 reverse() 메소드는 인자가 null인지 여부를 확인하지 않는데, 사용자는 빈 내의 reverse 메소드를 변경하여 이것을 고칠 수 있다. 한편 여기에 인터셉터를 이용할 수도 있으며, 실제로 사용자는 인터셉터를 이용하여 최적화를 추가할 수가 있다. 그러면 StringServiceInterceptor라 불리는 인터셉터를 정의하여 이 두 작업을 모두 수행해보기로 하자.
StringServiceInterceptor 인터셉터는 proceed()를 호출하여 호출 흐름을 지속하는 대신 먼저 인자가 null인지 혹은 인자가 단일 문자열인지 여부를 확인한다. 두 가지 경우 모두, 인터셉터는 단순히 인자를 반환하게 된다.
빈에 인터셉터 체인 추가하기
StringServiceInterceptor가 정의되었다면 이를 StringServiceBean에 추가해보기로 하자. 이를 위해서는 빈 내의 @Interceptors 주석을 이용해야 하는데, 이 주석은 인터셉터 클래스 체인을 지정한다.
위에 표시된 코드는 본 테크 팁의 텍스트 버전에서 갈무리할 수 있다.
예제 코드 실행하기
Java EE 5 SDK를 아직 구하지 못했다면 Java EE 다운로드 페이지 http://java.sun.com/javaee/community/gl ··· t_it.jsp
에서 다운로드 받아 설치한다.
예제 패키지 http://java.sun.com/mailers/techtips/en ··· rcpt.jar 를 <appsrv_install>/samples/javaee5/enterprise 디렉토리에 다운로드한다. 이 때, <appsrv_install>은 Java EE 5 애플리케이션 서버가 설치된 장소를 나타낸다. 각자의 환경을 <appsrv_install>/samples/javaee5/index.html 파일에서 기술한 대로 설정한다.
예제 패키지의 압축을 푼다. ttoct2006intrcpt 아래의 interceptor-techtip-ear 디렉토리에는 예제를 위한 소스 파일과 기타 지원 파일이 포함되어 있다.
interceptor-techtip-ear 디렉토리로 이동한다.
다음 명령어를 입력하여 애플리케이션 서버를 시작한다.
<appsrv_install>/bin/asadmin start-domain domain1
예제 애플리케이션을 구축하고 실행한다. interceptor-techtip-ear 디렉토리에서 아래 명령어를 입력한다.
요약
EJB 3.0 인터셉터는 보다 세련된 방법으로 엔터프라이즈 빈의 기능을 확장할 수 있게 해줄 뿐 아니라, 이식 가능한 방법으로 크로스커팅(crosscutting) 작업을 구현하는 데 특히 유용하다.
인터셉터에 건네지는 InvocationContext 객체는 비즈니스 메소드에 건네지는 매개변수 값을 검사하고 변경할 수 있는 다양한 메소드를 제공한다. 본 팁에 나와있는 StringServiceInterceptor는 InvocationContext.getParameters()를 이용하여 검증과 최적화를 수행한다.
EJB 인터셉터와 관련하여, 일련의 인터셉터를 손쉽게 변경할 수 있게 해주는 기본 인터셉터, 클래스 레벨 인터센터, 메소드 레벨 인터셉터 등과 같은 고급 주제들이 상당수 있는데, 이러한 주제들에 관해서도 향후 테크 팁에서 다루어 볼 예정이다.
EJB 3.0 인터셉터에 관한 보다 자세한 내용을 원할 경우 다음의 예제 애플리케이션들을 참고하기 바란다.
Interceptor-Stateless 세션 빈 예제 애플리케이션 https://glassfish-samples.dev.java.net/source/browse/*checkout*/glassfish-samples/ws/javaee5/enterprise/interceptor-stateless-ear/docs/index.html
Annotation-Override-ear 예제 애플리케이션 https://glassfish-samples.dev.java.net/source/browse/*checkout*/glassfish-samples/ws/javaee5/enterprise/annotation-override-interceptor-ear/docs/index.html
계속해서 메소드 레벨에서 인터셉터를 추가하는 방법을 설명하고 인터셉터 순서화(ordering)에 관해 자세히 논하기로 한다.
예제 빈
먼저 NumberUtilbean이라 명명된 간단한 stateless 세션 빈을 사용하여 본 팁의 개념들을 예시해 보기로 하자. 이 빈은 일련의 간단한 유틸리티 메소드를 제공하고 NumberUtil이라는 이름의 비즈니스 인터페이스를 구현한다.
umberUtil의 형태는 다음과 같다.
package com.sun.techtip2.interceptor;
import javax.ejb.Remote;
@Remote
public interface NumberUtil {
public byte[] intToBytes(int val);
public int bytesToInt(byte[] data);
public boolean isOddNumber(int val);
}
그리고 다음은 NumberUtilBean이다.
package com.sun.techtip2.interceptor;
import javax.ejb.Stateless
@Stateless
public class NumberUtilBean
implements NumberUtil {
public byte[] intToBytes(int val) {...}
public int bytesToInt(byte[] data) {...}
public boolean isOddNumber(int val) {...}
}
isOddNumber 메소드는 너무나 평범해서 클라이언트 모드에서 구현 가능했다는 점에 유의할 것. 단, 인터셉터 예시를 위해 메소드를 원격 인터페이스에 포함시켰다.
클래스 레벨 인터셉터
이전 팁에서는 인터셉터를 정의하고 이를 빈에 추가하는 방법에 대해 알아보았으며, 인터셉터의 한 예로, 인터셉트된 비즈니스 메소드를 실행하는 데 걸리는 시간을 프린트하는 메소드 프로파일러가 포함되었다. 이제 인터셉터로 사용될 메소드 프로파일러를 하나 더 작성해보도록 하자. 새로운 메소드 프로파일러는 NumberUtilBean 내의 각 메소드에 소요된 시간을 프린트한다.
이전 팁에서 이미 설명했듯이, 사용자는 @AroundInvoke 메소드를 통해 인터셉터를 정의하고 @Interceptors 주석을 이용하여 인터셉터를 클래스에 추가할 수 있다. 클래스 레벨에서 인터셉터를 추가하면 인터셉터의 around invoke 메소드가 클래스 내의 모든 비즈니스 메소드에 앞서 호출된다.
다음은 새로운 메소드 프로파일러인 MethodProfileInterceptor이다.
package com.sun.techtip2.interceptor;
import javax.interceptor.AroundInvoke;
public class MethodProfileInterceptor {
public MethodProfiler () {
}
@AroundInvoke
private Object recordStat(InvocationContext invCtx)
throws Exception {
long t1 = System.currentTimeMillis();
try {
invCtx.proceed(); //Continue execution
finally {
long t2 = System.currentTimeMillis();
System.out.println(invCtx.getMethod().getName() +
" took: " + ((t2 - t1)/1000.0 + " seconds");
}
}
}
다음은 클래스에 새로운 인터셉터가 추가된 NumberUtilBean이다.
package com.sun.techtip2.interceptor;
import javax.ejb.Stateless
import javax.interceptor.Interceptors;
@Interceptors(
{com.sun.techtip2.interceptor.MethodProfileInterceptor.class})
@Stateless
public class NumberUtilBean
implements NumberUtil {
public byte[] intToBytes(int val) {...}
public int bytesToInt(byte[] data) {...}
public boolean isOddNumber(int data) {...}
}
메소드 레벨 인터셉터
가령, 메소드로 패스된 인수가 null인지 여부를 확인하는 인터셉터를 NumberUtilBean에 추가하고 싶다고 하자. 사용자는 @Interceptors annotation을 사용하여 클래스에 인터셉터를 쉽게 추가할 수 있으며, 이 경우, 인터셉터의 around invoke 메소드는 각각의 빈 메소드에 앞서 호출된다.
그러나 인터셉터를 빈 내의 특정 메소드에 제한하고 싶다면 어떻게 해야 할까? 예컨대, 사용자가 NumberUtilBean 내의 intToBytes() 메소드에 패스된 파라미터가 null이 아닌지 검증하기를 원한다고 가정해보자. 이를 위해서는 클래스 레벨이 아닌 메소드 레벨에서 @Interceptors 주석을 지정해주어야 한다. 이렇게 하면 인터셉터의 around invoke 메소드가 특정 메소드 전에만 호출된다.
다음은 파라미터가 null인지 여부를 확인하는 인터셉터 코드이다.
import javax.interceptor.AroundInvoke;
public class NumberUtilArgumentsChecker {
public NumberUtilArgumentsChecker() {
}
@AroundInvoke
private Object validate(InvocationContext invCtx)
throws Exception {
Object[] params = invCtx.getParameters();
if (params.length == 0) {
throw new IllegalArgumentException(
invCtx.getMethod().getName()
+ "Requires a non null argument");
}
return invCtx.proceed();
}
파라미터가 null인 경우 예외 텍스트에 추가되는 메소드 이름을 얻기 위해 invCtx.getMethod().getName()이 어떻게 사용되는지에 대해 특히 주목할 것.
다음은 intToBytes() 메소드를 위한 새 인터셉터가 추가된 NumberUtilBean이다.
package com.sun.techtip2.interceptor;
import javax.ejb.Stateless
import javax.interceptor.Interceptors;
@Interceptors(
{com.sun.techtip2.interceptor.MethodProfileInterceptor.class})
@Stateless
public class NumberUtilBean
implements NumberUtil {
public byte[] intToBytes(int val) {...}
@Interceptors(
{com.sun.techtip2.interceptor.NumberUtilArgumentsChecker.class})
public int bytesToInt(byte[] data) {...}
public boolean isOddNumber(int data) {...}
}
인터셉터의 호출 순서 변경하기
클래스의 경우, 클래스 레벨과 메소드 레벨 모두에서 인터셉터가 정의되도록 할 수 있는데, 이 경우 인터셉터가 호출되는 순서를 아는 것이 특히 중요하다. EJB 3.0 스펙에서는 클래스 레벨 및 메소드 레벨 인터셉터가 모두 지정되는 경우 다음과 같은 호출 순서를 선언한다.
빈 클래스에서 정의되는 모든 인터셉터 클래스. 이 클래스 레벨 인터셉터는 @Interceptors 주석에서 지정되는 순서에 따라 호출된다.
메소드 레벨에서 지정되는 모든 인터셉터. 이 메소드 레벨 인터셉터는 @Interceptors 주석에서 지정되는 순서에 따라 호출된다.
빈 상의 인터셉터 메소드(있을 경우).
인터셉터 제외하기
메소드 프로파일러의 보기에서, 클래스 레벨 인터셉터 MethodProfileInterceptor는 isOddNumber() 메소드를 포함한 NumberUtilbean 클래스 내의 모든 메소드에 앞서 실행된다. 하지만 isOddNumber()의 구현은 너무나 평범해서 사실상 프로파일링이 필요하지는 않다.
사용자는 인터셉터를 클래스 레벨 인터셉터에서 제거하고 intToBytes() 및 byteToInt() 메소드만을 위한 메소드 인터셉터로 명확하게 지정함으로써 isOddNumber() 메소드 전에 실행되는 것을 막을 수 있다.
인터셉터를 메소드에서 제외시키는 더 쉬운 방법이 있는데, 즉 @ExcludeClassInterceptors 주석을 이용하여 클래스 레벨 인터셉터를 특정 메소드로부터 제외시키는 것이다. 그렇다면 @ExcludeClassInterceptors 주석을 이용하여 MethodProfileInterceptor가 isOddNumber() 메소드 전에 실행되는 것을 차단해보도록 하자.
다음은 @ExcludeClassInterceptors 주석이 추가된 NumberUtilBean이다.
package com.sun.techtip2.interceptor;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.interceptor.ExcludeClassInterceptors;
@Interceptors(
{com.sun.techtip2.interceptor.MethodProfileInterceptor.class})
@Stateless
public class NumberUtilBean
implements NumberUtil {
public byte[] intToBytes(int val) {...}
@Interceptors(
{com.sun.techtip2.interceptor.NumberUtilArgumentsChecker.class})
public int bytesToInt(byte[] data) {...}
@ExcludeClassInterceptors
public boolean isOddNumber(int data) {...}
}
이제 MethodProfileInterceptor는 intToBytes()와 bytesToInt() 메소드가 실행되기 전에만 호출된다.
예제 코드 실행하기
Java EE 5 SDK를 아직 구하지 못했다면 Java EE 다운로드 페이지에서 다운로드 받아 설치한다.
예제 패키지를 <appsrv_install>/samples/javaee5/enterprise 디렉토리에 다운로드한다. 이 때, <appsrv_install>은 Java EE 5 애플리케이션 서버가 설치된 장소를 나타낸다. 다음 파일에 기술된 내용대로 각자의 환경을 설정한다.
<appsrv_install>/samples/javaee5/index.html
다음 예제 패키지의 압축을 푼다.
jar xvf ttdec2006intrcpt_2.jar
interceptor_2 디렉토리로 이동한다. 이 디렉토리에는 예제를 위한 소스 파일과 기타 지원 파일이 포함되어 있다.
다음 명령어를 입력하여 애플리케이션 서버를 시작한다.
<appsrv_install>/bin/asadmin start-domain domain1
예제 애플리케이션을 구축하고 실행한다. interceptor_2 디렉토리에서 아래 명령어를 입력한다.
ant all
}
bpp-run-app-client:
[echo] running application client container.
[exec] intToBytes(1234) ==> 0 0 4 -46
[exec] bytesToInt(intToBytes(1234))) ==> 1234
[exec] isOddNumber(7) ==> true
요약
EJB 3.0 인터셉터는 세련된 방법으로 엔터프라이즈 빈의 기능을 확장할 수 있게 해줄 뿐 아니라, 이는 이식 가능한 방식으로 크로스커팅(crosscutting) 작업을 구현하는 데 특히 유용하다.
이 테크 팁에서는 인터셉터를 소개하고 클래스 레벨 인터셉터의 사용법을 예시한 바 있으며, 클래스 레벨 인터셉터는 빈 내의 모든 비즈니스 메소드에 앞서 실행된다. 본 팁에서는 메소드 레벨 인터셉터를 소개하고 이들 인터셉터가 어떻게 클래스 레벨 인터셉터와 더불어 특정 메소드를 위해 실행되는지를 예시해보았다.
또한, 세련된 방법으로 클래스 레벨 인터셉터가 특정 메소드 전에 실행되는 것을 차단할 수 있게 해주는 @ExcludeClassInterceptors 주석의 사용법에 대해서도 알아보았다.
이밖에도, 기본 인터셉터와 기본 인터셉터의 제외 등, EJB 인터셉터와 관련한 수준 높은 주제들이 많이 있다. 또한, 인터셉터는 의존성 삽입(dependency injection)을 선언할 수 있고, 이는 컨테이너가 리소스 및 엔터프라이즈 빈을 인터셉터에 삽입할 수 있게 해준다.
저자 소개
Mahesh Kannan은 EJB 컨테이너 팀 소속으로, 지난 6년간 꾸준히 Java EE 개발에 참여해 왔다.
DEVELOPER ASSISTANCE
Need programming advice on Java EE? Try Developer Expert Assistance. http://developers.sun.com/services/expertassistance/
이 문서에 사용된 코드/정보에 대한 라이센스는 license terms 페이지에서 확인하실 수 있습니다.
"Java EE" 카테고리의 다른 글
- Attach API (댓글 18개 / 트랙백 1개) 2007/09/03
- JSP 2.0 EXPRESSION LANGUAGE (댓글 1개 / 트랙백 0개) 2004/02/05
- 환경 엔트리를 이용해서 배포의 사용자 정의하기 (댓글 2개 / 트랙백 0개) 2003/12/24
- JAX-WS를 이용한 웹 서비스 개발 (댓글 1개 / 트랙백 0개) 2006/01/18
- EJB 2.1로 메시지 구동 빈 이용하기 (댓글 1개 / 트랙백 0개) 2005/05/18
- EclipseLink를 사용하여 JPA에서 반복 불가능한 읽기 방지 (댓글 0개 / 트랙백 1개) 2008/07/09
- 컴포넌트 시스템과 클래스 로더 경계 (댓글 1개 / 트랙백 0개) 2004/10/05
- JAX-WS Dispatch 및 Provider API를 이용한 문서 처리 (댓글 4개 / 트랙백 0개) 2006/09/15
- POJO를 Persistent Entity로 변환하기 (댓글 1개 / 트랙백 0개) 2005/12/27
- SAAJ 소개 (댓글 1개 / 트랙백 0개) 2005/06/08
댓글을 달아 주세요
자바 코드에 대해 조금이나마 알 것 같아요
2007/09/07 20:19감사합니다 좋은 글
예제 코드 실행하기..많은 도움이 되었읍니다
2007/09/15 21:49좋은 정보 감사해요~
2007/09/19 03:59자바는 알면 알수록 재미가 있어지네요.
2007/09/19 15:47좋은 정보 많아요.
2007/09/19 23:20많이 배우고 가요~
감사드려요~