Java/spring2009/01/31 14:45
이번엔 @Aspect Annotation을 이용해서 AOP를 설정해보도록 하자....
이것 역시 XML 스키마를 이용하여 설정한다..



일단 xmlns에 aop를 추가해주고...



위 태그를 사용하면 @Aspect Annotation이 사용된 클래스의 Advice 및 Pointcut을 알맞은 빈 객체에 적용한다...
실제 @Aspect Annotation을 사용한 클래스를 살펴보자..



앞서 살펴보았던 POJO를 이용한 설정과 매우 유사하다..
단지 소스 코드에 직접 입력하느냐 설정 파일에 설정하느냐의 차이만 있을 뿐이다...
위의 TestAspect를 생성해주면 test.aspect 패키지의 모든 클래스의 모든 메소드에 AOP가 적용이 된다..



위의 예제는 test.aspect.TestService의 모든 메소드에 TestAspect가 적용되는 설정이다...
그렇다면 도대체 AspectJ의 Pointcut 표현식이 어떤 뜻인지 궁금해진다..

Spring에서 제공하는 AspectJ의 Pointcut 명시자는 execution과 within 그리고 bean 명시자이다..
먼저 Advice를 적용할 메소드를 명시할 때 사용하는 execution부터 살펴보도록 하자..

execution([접근명시자패턴] 리턴타입패턴 이름패턴(파라미터패턴)) 접근명시자패턴은 public, protected, private 등을 사용할 수가 있고 생략 가능하다..
리턴타입패턴은 가능한 모든 리턴 타입을 사용할 수 있고..
이름패턴에는 패키지를 포함한 클래스 이름과 메소드 이름을 사용하고...
파라미터패턴에는 매칭될 파라미터에 대해 명시해준다...
또한 모든 패턴에는 * 를 이용하여 모든 값을 명시할 수 있고 ..을 이용하여 0개 이상이라는 의미를 표현할 수 있다..
예제를 살펴보자....

execution(public void set*(..)) : 리턴 타입이 void이고 set으로 시작하는 파라미터 0개 이상인 public 메소드
execution(* test.aspect.*.*()) : test.aspect 패키지의 파라미터가 없는 모든 메소드
execution(* test.aspect..*.*(..)) : test.aspect 패키지 및 하위 패키지에 파라미터가 0개 이상인 메소드
execution(void test.aspect.TestService.test()) : 리턴 타입이 void인 TestService의 test 메소드 호출
execution(* test*(*)) : test로 시작하는 파라미터 1개를 갖는 메소드
execution(* test*(*, *)) : test로 시작하는 파라미터 2개를 갖는 메소드
execution(* test*(Integer, ..)) : test로 시작하고 첫번째 파라미터는 Integer이고 1개 이상의 파라미터를 갖는 메소드

자세한 내용은 첨부로 올리는 AspectJ Syntax를 참고하도록 하자..

within 명시자는 특정 메소드가 아닌 특정 타입에 속하는 메소드를 명시할 때 사용한다...

within(test.aspect.TestService) : TestService의 모든 메소드
within(test.aspect.*) : test.aspect 패키지의 모든 메소드
within(test.aspect..*) : test.aspect 패키지 및 하위 패키지의 모든 메소드

bean 명시자는 빈의 이름을 이용하여 Pointcut을 정의한다.

bean(test) : 이름이 test인 빈의 메소드
bean(*test) : 이름이 test로 끝나는 빈의 메소드


이상 Spring에서 제공하는 AspectJ 표현식을 알아보았다..
위의 각각의 표현식은 다음과 같이 &&와 ||를 이용하여 연결할 수 있다..

@After("execution(public * get*()) ||execution(public * set*(..))")

이상으로 Spring의 AOP에 대하여 알아보았다.

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by brain jjune
TAG Spring
Java/spring2009/01/31 14:41

Spring 2.x 버전부터는 API를 사용하지 않고 POJO를 이용해서 AOP를 적용할 수 있다..
POJO를 이용한 AOP를 위해서는 ASM 라이브러리가 필요로 한데..
2.5 버전은 spring.jar에 포함되어 있으니 신경쓸 필요 없다...
자 그럼 한 번 살펴보도록 하자...

먼저 POJO 기반 AOP는 XML 스키마를 이용하여 설정해준다..
태그에 아래처럼 xmlns를 추가해준다...



aop란 네임 스페이스를 등록했다면 다음과 같이 AOP 설정을 한다...



먼저 test 빈은 공통 기능을 구현한 빈이라고 가정하자...
AOP 설정은 태그로 시작하고 태그의 ref 속성에는 공통 기능을 구현한 빈을 넣어준다..
위의 예제에서는 test 빈이 공통 기능을 구현했다고 했으므로 ref에는 test가 설정되어있다...
태그에는 Advice를 적용할 Pointcut을 expression 속성에 AspectJ 표현식으로 설정해준다...
AspectJ 표현식은 잘 모르는 관계로 관심 있는 사람은 따로 찾아보도록 하자...
아무튼 위의 표현식은 test.service 패키지와 그 하위 패키지에 있는 모든 클래스에 대해서...
public인 모든 메소드에 대하여 Pointcut을 지정하겠다는 의미라고 한다...
Advice를 설정하기 위해 위의 예제에는 란 태그가 쓰였는데...
pointcut-ref 속성에는 위에 지정한 Pointcut을 지정하고 method에는 실제 실행할 메소드를 지정한다...
위의 예제에서는 test 빈의 testMethod를 호출하겠다는 말이 된다...

Advice를 지정하기 위해 말고도 사용할 수 있는 태그는 다음과 같다..

메소드 실행 전에 적용
메소드가 정상 실행된 후에만 적용
메소드가 예외를 발생시킬 때에만 적용 (catch문과 비슷)
메소드가 실행된 후 무조건 적용 (finally문과 비슷)
모든 시점에 적용


Advice 설정에는 pointcut-ref 속성 말고 pointcut이란 속성도 있는데...
pointcut 속성에는 태그의 expression 속성을 바로 설정해줄 수 있다...
위의 예제는 아래와 같이 사용할 수 있다..



설정은 다 살펴보았다...
이제 직접 Advice를 만들어 보도록 하자..
POJO를 이용하기 때문에 일반 자바 클래스를 만들듯이 만들면 된다...
각 Advice별로 알아보도록 하자..

를 이용한다면 Advice 클래스에는 다음과 같은 메소드를 구현해준다..



대상 객체나 호출 메소드 정보를 얻고자 한다면 매개변수로 JoinPoint를 넘겨준다..

은 다음과 같다..



역시나 추가적인 정보가 필요하다면 매개변수로 JoinPoint를 넘겨주자..
만약 메소드의 리턴 값을 사용하고자 한다면 매개변수에 추가로 Object를 넘겨주고...
태그의 returning 속성에 위의 메소드에서 정의한 매개변수 이름을 넘겨준다...
즉... 다음과 같이 설정해주면 된다....



여기서 한가지 주의할 점은.. 만약 JoinPoint를 매개변수로 넘기고자 할 때는..
꼭 첫 번째 매개변수로 넘겨야 한다는 사실이다..
그렇지 않으면 예외가 발생하게 된다...

이번엔 을 알아보자...



추가 정보가 필용하면 JoinPoint를 넘겨주자..
만약 발생한 예외가 필요한 경우 매개변수에 해당 예외 클래스를 넘겨주고...
태그의 throwing 속성에 위의 메소드에서 정의한 매개변수의 이름을 넘겨준다..



마지막으로 태그를 알아보자...
이건 이전 글에서 봤던 MethodInterceptor와 같은 기능을 하는 태그이다..



같은 경우에는 무조건 매개변수로 ProceedingJoinPoint를 전달받아야 한다..
그리고 대상 객체의 메소드를 실행하려면 꼭 proceed 메소드를 호출해야 한다...
저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by brain jjune
TAG AOP
Java/spring2009/01/31 14:33
AOP에서 사용하는 용어부터 알아보자
1. Aspect
여러 클래스에 공통으로 적용되는 공통 관심 사항 (로깅, 트랜잭션, 보안 등)

2. Advice
특정 Joinpoint에 실행하고자 하는 것

3. Joinpoint
메소드 호출 전이나 호출 후와 같이 프로그램이 실행되는 동안의 특정한 시점

4. Pointcut
Joinpoint의 집합으로 언제 Advice가 적용되어야 하는지 설명

5. Weaving
실제로 Advice를 핵심 관심 사항에 적용하는 것
컴파일시, 클래스 로딩시, 런타임시 3가지 방식이 있다..

Spring에서는 런타임시에 AOP를 적용하는 방식을 사용하고 있다...
즉 소스 코드나 클래스 정보를 변경하지 않고 프록시 패턴을 사용하여 AOP를 적용하게 된다..
따라서 메소드가 호출될 때만 적용할 수 있고 필드값 변경과 같은 Joinpoint는 처리할 수 없게 된다...
좀 더 풍부한 AOP 기능을 적용하고자 한다면 AspectJ를 사용하는 것이 나을 것이다...
자.. 그러면 Spring에서 어떻게 AOP를 구현하는지 자세히 살펴보자...

일단 Spring에서는 AOP를 구현하기 위한 3가지 방식을 제공한다...
Spring API를 이용한 구현, POJO를 이용한 구현, @Aspect Annotation을 이용한 구현 3가지이다..
위의 3가지 모두 프록시 패턴을 사용하여 AOP가 구현되므로 메소드가 호출될 때만 AOP가 적용될 수 있다는 것을 명심하자..
프록시 객체를 생성하는 방식은 인터페이스를 구현하고 있느냐 아니냐에 따라 2가지로 나뉘게 된다...
대상 클래스가 인터페이스를 구현하고 있다면 리플렉션 API인 java.lang.reflect.Proxy를 이용하여 프록시 객체를 생성하고...
구현하고 있지 않다면 CGLIB를 이용하여 프록시 객체를 생성하게 된다..
인터페이스를 구현하고 있다면 인터페이스를 기반으로 프록시 객체를 생성하므로...
인터페이스에 정의되지 않은 메소드에는 AOP가 적용되지 않는다..
CGLIB는 대상 클래스를 상속받아 프록시 객체를 생성하므로 final 클래스인 경우 생성할 수 없으며...
final 메소드에는 AOP가 적용되지 않는다...

Spring API를 이용한 AOP 구현 방법부터 알아보도록 하자...
Spring은 AOP를 구현하는데 필요한 Advice 인터페이스, Pointcut 인터페이스 ProxyFactoryBean 클래스를 제공하므로...
다음과 같은 과정을 통해 구현하면 된다...

1. Advice 클래스 작성
2. Pointcut 설정
3. Advice와 Pointcut을 묶은 Advisor 설정
4. ProxyFactoryBean을 이용하여 대상 객체에 Advice 설정

자.. 그러면 이제 Advice 클래스를 작성해보자...
Advice 클래스는 다음 4가지 인터페이스 중 하나를 구현하여 사용하면 된다..

org.springframework.aop.MethodBeforeAdvice : 메소드가 실행되기 전
org.springframework.aop.AfterReturningAdvice : 메소드가 실행된 이후
org.springframework.aop.ThrowsAdvice : 예외가 발생한 경우
org.aopalliance.intercept.MethodInterceptor : 위의 3가지를 모두 합쳐 놓은 것


MethodBeforeAdvice는 다음과 같은 메소드가 정의되어 있다...
void before(Method method, Object[] args, Object target) throws Throwable;


method는 대상 객체에서 실제로 호출될 메소드를 나타내는 것으로 자바 리플렉션을 참고하도록 하자...
args는 메소드 호출시 전달된 매개변수 목록을 나타낸다...
마지막으로 target은 실제 메소드를 호출할 대상 객체를 나타낸다...


위와 같이 before 메소드에 원하는 기능을 구현해주면 된다...
물론 before 메소드에서 전달된 메소드를 호출하면 실제 호출되는 메소드가 2번 호출되고..
before 메소드에서 예외가 발생하면 대상 객체의 메소드는 실행되지 않는다...

AfterReturningAdvice는 다음과 같은 메소드가 정의되어 있다...

void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;


returnValue는 대상 객체의 메소드가 리턴한 값이다...
하지만 리턴 값을 변경할 수는 없다..
나머지 매개변수는 위에서 살펴본 before 메소드와 동일하다...
afterReturning 메소드는 대상객체의 메소드가 정상 실행을 완료한 이후에 실행된다...
따라서 대상 객체에서 예외가 발생할 경우 이 메소드는 실행되지 않는다..



이번에는 예외가 발생할 경우에 사용하는 ThrowsAdvice를 살펴보도록 하자...
이 인터페이스는 아무런 메소드도 정의하고 있지 않다...
당황스럽지만 아래와 같은 형태로 메소드를 구현해주면 된다...

public void AfterThrowing(/*Method method, Object[] args, Object target*/, ExceptionType e)


자신이 처리할 ExceptionType만 명시해주면 된다..
물론 메소드를 오버로딩 해서 여러개 만들어도 상관 없다..
그리고 앞에 주석으로 막아 놓은 부분은 있어도 되고 없어도 된다...
예외를 로깅하고자 한다면 자세한 내용을 알기 위해서는 필요할 것이라 생각된다..



마지막으로 MethodInterceptor를 알아보도록 하자..
이 인터페이스는 앞서 살펴본 3가지 모두 합쳐 놓은 인터페이스이다..
즉 3가지 경우를 모두 사용할 수 있다는 말이다..
MethodInterceptor는 다음과 같다...



이 인터페이스는 앞서 살펴본 인터페이스들과는 달리 대상 객체를 호출하지 않는다는 점이다...
반드시 invoke 메소드에서 직접 대상 객체의 메소드를 호출해야 한다..
예제를 보도록 하자...



예제를 보면 MethodInvocation 객체의 proceed 메소드를 호출하는 것을 볼 수 있다..
저 메소드를 호출하지 않으면 실제 타겟의 메소드가 호출되지 않으니 꼭 호출해주도록 하자...
만약 어떤 조건이 있어서 호출하지 않겠다 하면 호출하지 않아도 된다.... -_-;;
자.. 이제 Advice를 만들었으니 설정 파일에서 빈을 만들어주자..



여기까지가 Advice를 만드는 과정의 끝이고 이번에는 Pointcut을 설정해보도록 하자...
Advice를 만들었으면 이 Advice를 어떤 Joinpoint에 적용할지 저정해야 한다..
따라서 Pointcut을 만들어주도록 하자...
Pointcut을 만들 수 있는 클래스는 아래의 3가지 종류가 있다...

org.springframework.aop.support.JdkRegexpMethodPointcut : 자바 1.4부터 지원하는 정규 표현식을 사용하여 정의
org.springframework.aop.support.Perl5RegexpMethodPointcut : 자바 1.3 이하 버전인 경우 펄 5의 정규 표현식을 사용하여 정의
org.springframework.aop.aspectj.AspectJExpressionPointcut : AspectJ의 표현식을 이용하여 정의

AspectJExpressionPointcut은 이전에 퍼온 글에서 사용하던 AspectJ 문법을 이용한다..
따라서 JdkRegexpMethodPointcut을 사용하는 법만 살펴보고 넘어가도록 한다..
정규표현식에 대한 내용도 이전에 퍼온 글을 참고하도록 한다...



위와 같이 설정해주면 Method로 끝나는 함수 호출시마다 AOP를 적용시켜주게 된다...
물론 아래와 같이 여러가지 패턴을 적용할 수도 있다...
get이나 set 메소드에 적용하는 예이다..



그럼 이제는 어떤 Advice에 어떤 Pointcut을 적용시킬지 결정하는 Advisor를 설정해보자...
Advisor는 다음 2가지 종류가 있다...

org.springframework.aop.support.DefaultPointcutAdvisor : Advice와 Pointcut을 연결시킨다.
org.springframework.aop.support.RegexpMethodPointcutAdvisor : 정규표현식으로 Pointcut을 자동 생성해준다.

DefaultPointcutAdvisor는 아래와 같이 연결할 Advice와 Pointcut을 명시해주면 된다...



RegexpMethodPointcutAdvisor은 Pointcut을 만드는 수고를 덜어준다...
아래 4줄이면 위의 DefaultPointcutAdvisor처럼 pointcut1 설정을 해주지 않아도 동일한 결과를 갖는다..



물론 pattern 속성에는 Pointcut 설정과 마찬가지로 를 이용하면 여러개의 패턴을 적용할 수 있다...

자.. 이제 마지막으로 ProxyFactoryBean을 사용하여 대상 객체에 Advice를 적용해보도록 하자..
Spring에서는 AOP를 적용할 때 프록시 패턴을 사용하는데...
인터페이스를 구현하지 않는 경우와 구현하는 경우에 따라서 설정도 약간 다르다...
먼저 인터페이스를 구현하지 않는 경우를 살펴보자...



target에는 AOP를 적용할 대상 빈을 넣어주고...
적용할 Advice 혹은 Advisor를 interceptorNames 속성에 넣어주면 모든 설정이 끝난다..
Advice를 넣는 것과 Advisor를 넣는 것은 약간의 차이가 있는데..
Advice는 Pointcut이 지정되어 있지 않기 때문에 무조건 AOP 적용 대상이 되는 것이고..
Advisor는 Pointcut이 지정되어 있으므로 해당 조건에 맞을 때만 AOP가 적용된다..
특히 주의해야 할 점은 proxyTargetClass 속성의 값을 꼭 true로 주어야만 한다는 것이다...
그리고 lib의 cglib 폴더에 있는 CGLIB도 프로젝트에 추가해주어야 한다...
인터페이스를 구현하는 경우는 아래와 같다..



target 객체가 어떤 인터페이스를 구현하고 있는지 proxyInterfaces 속성에 설정해준다...
인터페이스를 구현하는 경우에는 인터페이스에 등록이 되어 있는 메소드에만 AOP가 설정 가능하는 것을 명심하자...

왜 그런지는 아래 실제 사용하는 코드를 보면 알 수 있다...
인터페이스를 구현하는 경우와 구현하지 않는 경우에 따라 사용하는 코드도 다르다...



위의 코드를 보면 알 수 있듯이..
인터페이스를 구현하지 않는 경우에는 AOP를 적용할 실제 대상 객체로 캐스팅하여 사용하기 때문에...
대상 객체에 있는 모든 메소드에 적용할 수 있는 것이다..
그에 비해 인터페이스를 구현하는 경우에는 해당 인터페이스로 캐스팅하여 사용하야 하기 때문에...
해당 인터페이스에 정의되어 있지 않은 메소드에는 절대 사용할 수가 없는 것이다..

그렇다면 Advice가 적용되는 순서는 어떻게 될까...
메소드를 호출하기 전에는 interceptorNames에 등록한 순서대로 적용한다..
위의 proxy1을 예로 들면 throwAdvice를 적용하고 advisor1을 적용하게 된다...
물론 BeforeAdvice이거나 MethodInterceptor인 경우에만 적용한다... 당연하다... -_-;;
메소드를 호출한 후에는 위와 반대의 순서대로 동작한다..
이것도 물론 AfterReturningAdvice 처럼 적용 가능한 경우 적용한다... 이것도 당연하다... -_-;;

휴.. 길다.. AOP를 설정하기가 이렇게 빡시다..
그래서 자동으로 프록시를 생성하도록 하는 DefaultAdvisorAutoProxyCreator 클래스를 이용할 수도 있다..



위와 같이 ProxyFactoryBean 대신에 DefaultAdvisorAutoProxyCreator만 설정해주면 알아서 프록시를 만들어준다..
이 클래스를 사용하면 아래처럼 프록시 빈의 id를 사용하는 대신 원래의 대상 빈의 id를 사용할 수 있게 된다..



저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by brain jjune
TAG AOP, Spring