Spring 2.0(M2기준) 에서의 타입화된 Advice
<?xml version="1.0" encoding="UTF-8"?> |
<aop:config> |
public class AdviceBindingTestAspect { |
public aspect AdviceBindingTestAspect { |
인자 이름과 타입을 결정하기
- <:aop:advice> 요소내 arg-names속성을 사용하여 명시될수 있다. 예제에서 우리는 여기까지는 작업중이다. 우리는 예제에 arg-names="age"라고 명시할수 있다. 이 속성의 문자열 값은 인자 이름에서 콤마로 분리된 목록을 가진다.
- aspect를 정의하기 위해 @AspectJ 표기를 사용한다면, 모든 advice표기(@Before, @AfterReturning, @AfterThrowing 등등)는 선택적인 "argNames" 속성을 지원한다. 다시 언급하지만 이것은 콤마로 구분되는 인자 이름의 목록을 가진다.
2. 인자 이름이 명시적으로 선언되지 않는다면, 메소드를 정의하는 클래스의 클래스 파일로 부터 인자이름을 알아내도록 시도한다. 이것은 클래스가 디버그정보(최소한 -g:vars을 사용하는)를 가지고 컴파일되는한 가능하다. 사용자 관점에서 이것은 인자 이름을 얻기 위한 가장 쉽고 가장 자연스러운 방법일것이다. 디버그 정보를 가지고 컴파일하는것은 수행중인 코드내 문제를 인식하는 것을 좀더 쉽게 해준다. 혹시나 오버헤드를 걱정한다면, -g:vars는 클래스 파일이 Spring이 작업에 필요한 기법을 사용하는데 최소한의 디버그 정보(LocalVariableTables)를 가지도록 해줄것이다. g:vars의 부정적인 측면은 무엇인가..? 당신의 클래스 파일은 다소 커질것이다. 반면에 컴파일러가 만드는 최적화는 생성되지 않을것이고 애플리케이션의 역공학(리버스 엔지니어링-reverse engineering)은 좀더 쉬워진다. 만약 최적화의 손실을 걱정한다면, 스스로 여러가지 성능 테스트를 해보라.
3. local변수 테이블 정보는 메소드에 사용될수 없다. Spring은 pointcut표현과 메소드의 시그너처로부터 인자 이름을 추측하도록 시도한다. 메소드가 2개의 파라미터(참조 타입의 하나와 원시 타입의 하나)를 가진다고 생각해보자. "this()"를 사용하는 하나의 변수와 "args()"를 사용하는 하나의 변수를 연결하는 pointcut표현이 주어지면, "this"가 참조타입이어야만 하기 때문에 우리는 원시 인자가 args에 의해 바운드되어야만하는 것을 안다. pointcut을 바인딩하는 모든 AspectJ pointcut언어에서 지시어(designators)는 두가지 형태를 가진다. 하나는 타입명이고 하나는 변수명을 가지는 것이다. 예를 들어, 나는 "this"가 Float의 인스턴스인 join point에 간단히 대응되는 "this(Float)"를 작성할수 있거나 "this"가 "f"타입의 인스턴스이고 "f"인자에 값을 연결하는 join point에 대응되는 "this(f)"를 작성할수 있다. pointcut표현내 무슨 변수가 있는지 찾기 위해, Spring은 바인딩된 pointcut표현내 어느 문자열이 유효한 자바 확인자(identifier)명이고 소문자로 시작하는 것은 변수이고 나머지는 타입이라는 추측을 한다.
타입화된 advice사용하기
사용가능한 몇몇 기능을 보자.
현재 join point에 대한 정보
먼저, advice메소드가 org.aspectj.lang.JoinPoint타입의 첫번째 인자를 가진다면, JoinPoint인스턴스는 현재 join point가 advice메소드로 전달될것을 표시한다. 이것은 AspectJ advice범위내 'thisJoinPoint' 를 사용하는 동등한 기능을 제공한다. 이것은 당신에게 join point아래에서 수행되는 메소드와 같이 join point에 대해 반영하는(reflective) 정보를 전달할수 있기 때문에 JoinPoint객체는 넓은 범위의 join point에 적용하는 일반적인(generic) advice를 작성할때 유용할수 있다. around advice를 위해, 당신은 JoinPoint대신에 org.aspectj.lang.ProceedingJoinPoint를 사용해야만 한다. JoinPoint의 하위타입은 당신이 join point아래에서 계산(computation)을 처리하고자 할때 호출해야만 하는 중요한 "proceed"메소드를 제공한다.
JoinPoint에 대한 대안물처럼, org.aspectj.lang.JoinPoint.StaticPart타입의 첫번째 파라미터는 JoinPoint대신에 JoinPoint.StaticPart 의 인스턴스로 전달될것이다. 이것은 join point에 대한 정적으로 사용가능한 정보(예제를 위한 메소드와 시그너처)만을 제공한다. 하지만 호출을 위한 인자와 target객체는 충고(advised)되지 않는다. JoinPoint대신에 JoinPoint.StaticPart를 사용하는 것은 추후에 AOP프레임워크내부에서 최적화를 허용하게 할지도 모른다. 현재 Spring AOP를 사용할때 JoinPoint보다 더 효과적인것은 없다.
이것은 테스트묶음으로부터 JoinPoint를 사용하는 간단한 예제이다.
advice정의를 위한 XML의 일부:
<aop:advice |
advice method선언:
public void needsJoinPoint(JoinPoint tjp) { |
적절한 JoinPoint객체을 확인하는 테스트 케이스가 advice메소드로 전달된다.
public void testNeedsJoinPoint() { |
메소드 실행 인자
메소드 실행 join point에서 인자를 연결하는 "args" 예제를 이미 보았다(Spring AOP는 오직 메소드 실행 join point만을 지원한다). 당신은 "args"(와 AspectJ웹사이트의 AspectJ프로그래밍 가이드의 다른 모든 AspectJ pointcut지시어)를 볼수 있다. "args"는 인자의 수와 일치하는 메소드를 위한 실행 join point에만 대응하는 것을 제한하고 하나 이상의 인자에 연결할수 있다.
여기에 몇가지 간단한 예제가 있다.
-
args() - 인자가 없는 메소드 실행에 대응(여기서 바인딩하는것은 명백하게 없다.)
-
args(..) - 하나도 없거나 하나 이상의 인자를 가지는 메소드 실행에 대응(하지만 여전히 바인딩은 수행하지 않는다.)
-
args(x,..) - 하나나 그 이상의 파라미터를 가지는 메소드에 대응, 첫번째 파라미터는 "x"타입이어야 한다. 그리고 "x"에 인자값을 바인딩한다.
-
args(x,*,*,s,..) - 좀더 인공적인 예제. 적어도 4개의 파라미터를 가지는 메소드에 대응. 첫번째 파라미터는 "x"타입이어야만 하고 인자값은 "x"에 바인딩될것이다. 두번째와 세번째 파라미터는 어떤타입도 될수 있다. 하지만 4번째 파라미터는 "s"타입이어야만 하고 인자값은 "s"에 바인드될것이다.
"*" (어떤 타입의 하나의 인자와 대응되는), 와 ".." (어떤 타입의 0개 또는 여러개의 인자와 대응되는)의 명명된 인자의 조합으로, 당신은 join point에서 당신이 실행할 필요가 있는 advice의 컨텍스트 정보를 정확하게 보여줄수 있다.
메소드 반환값
당신의 advice가 메소드 실행 join point의 값을 반환하기 위해 접근할 필요가 있다면, 당신은 afterReturning advice를 사용할수 있고 "returning" 속성을 사용하여 반환되는 값을 바인드할수 있다.
다음은 간단한 예제이다.
<aop:advice |
메소드 정의와 묶어보자.
public void afterGettingName(String name) { |
"afterGettingName"은 'name' 파라미터처럼 전달되는 반환값을 가지는 "getName"의 호출로부터 성공적으로 반환하도록 호출될것이다.
"returning" 요소는 여기서 두가지를 수행한다. String('name'의 타입인)의 인스턴스를 반환하는 메소드 실행 join point만을 대응하는 것을 제한하고 파라미터 이름에 반환값을 바인딩한다.
타입명(변수명보다)을 가진 returning속성을 사용한다면, 반환값이 주어진 타입의 인스턴스인 그런 join point에만 대응하는것을 제한한다(하지만 advice에 실질적인 반환값을 전달하지는 않는다.).
던져진 예외
afterThrowing advice는 던져진 예외에 충고된(advised) join point가 존재할때 수행한다. 종종 이것은 advice내 던져지는 실질적인 예외에 접근하기위해 유용하다. 당신은 이것을 하기 위해 "throwing"속성을 사용할수 있다.
다음의 aspect정의를 자세히 보자.
<aop:aspect id="legacyDaoExceptionTranslator" ref="exceptionTranslator"> |
그리고 첨부된 메소드 정의
public void translateException(HibernateException hibEx) { |
advice는 오래된(Spring에 의해 작성되지 않은) DAO메소드가 Spring DataAccessException로 변환되고 다시 던져질수 있는 advice메소드에 던져진 예외를 전달하는 HibernateException를 던질때마다 수행될것이다.
실행중인 인스턴스
에소드의 실행은 "testBean.getAge()"에 대한 호출의 결과로 생긴다. pointcut지시어인 "this"와 "target"는 이 testBean인스턴스에 접근한다. AspectJ에서, this와 target 모두 join point에서 실행 인스턴스에 바인딩된다. Spring AOP에서, 충고된(advised) 객체는 프록시화 될것이다. "this" pointcut지시어를 사용하는 것은 프록시 객체(bean을 표시하는 객체 인스턴스를 위한 "this" 의 값)로 바인딩되고 "target" pointcut지시어를 사용하는 것은 진짜 target으로 바인딜될것이다.
예를 들면
<aop:advice |
이 advice는 다음의 테스트 케이스를 전달한다.
public void testOneIntAndOneObjectArgs() { |
만약 우리가 "this": "execution(* setAge(..)) 와 args(age) 와 target(bean)"대신에 "target"를 사용한다면, 테스트는 실패할것이다. 모의 협력자(mock collaborator)는 인자로 전달되기 위한 "testBean"(AOP프록시가 될)을 기대한다. 하지만 우리는 "target"를 사용하기 때문에, 프록시 target객체는 대신 전달될것이다. 이것은 다음의 테스트 케이스에 의해 설명된다.
public void testTargetBinding() { |
주석(Annotations)
AspectJ pointcut언어는 주석을 매치하고 바인딩하기 위한 풍부한 지원을 제공한다. Spring애플리케이션을 위한, 두개의 가장 유용한 pointcut지시어가 아마도 "@annotation" 과 "@within" 일것이다.
"@annotation"은 join point의 대상(subject)이 주어진 타입의 주석을 가질때 대응된다. Spring을 위해, 이것은 충고된(advised) 메소드가 주어진 타입의 주석을 가지는 것을 의미한다. 예를 들면, advice는 ..
<aop:advice |
"beforeTxMethodExecution" 메소드("tx"가 대응할 주석의 타입을 결정하기 위해)의 시그너처와 묶일때
public void beforeTxMethodExecution(Transactional tx) { |
"@Transactional" 주석을 가지는 bean메소드의 실행에 대응될것이다. Spring이 오직 실행 join point에 무조건 제한되는것을 기억하라. 전체적인 AspectJ언어를 사용할때 일치하는 pointcut은 "execution(* *(..)) && @annotation(tx)"가 될것이다.
"@within"은 주어진 주석을 가지는 타입내 join point와 대응된다. 그래서 타입내 bean메소드의 수행에 대응하는 것은 우리가 작성할수 있는 "@Transactional"를 가진다.
<aop:advice |
@AspectJ 스타일
모든 예제에서, 나는 당신에게 aspect정의의 XML스키마 형태를 사용한것을 보였다. Spring 2.0은 "@AspectJ 스타일"을 사용하여 작성된 aspect를 사용할수 있다. 이것은 이 볼로그의 범위를 넘어서지만, 여기에 이것이 작동하는 것의 간단한 예제가 있다.
당신이 할 필요가 있는 첫번째는 애플리케이션 컨텍스트내 @AspectJ aspect로부터 자동-프록시를 가능하게 하는것이다. 이것은 AOP를 위해 새로운 스키마 지원을 사용할때 필요하다.
<aop:aspectj-autoproxy/> |
만약 우리가 애플리케이션 컨텍스트내 실질적인 @AspectJ aspect(AspectJ주석을 사용하여 주석처리된)인 bean을 정의한다면, Sprng은 AOP프록시를 설정하기 위해 bean을 자동으로 사용할것이다. @AspectJ aspect로 작성된 전통적인 메소드 실행 예제는 다음과 같을것이다.
@Aspect |
이 aspect를 사용하기 위해, 우리는 다음처럼 정의를 추가할것이다.
<bean class="...AnnotationDrivenTransactionManager" > |
물론, 일반적인 모든 의존성삽입 기능은 aspect를 설정하기 위해 사용가능하다.
aspect정의에서 이 스타일을 사용하는 가장 멋진 장점은, 당신이 설정에서 "aspectj-autoproxy"요소를 제거하여 간단히 AspectJ 직조(weaving)하기 위한 Spring AOP프록시를 사용하여 전환할수 있고 대신 AspectJ를 사용하여 애플리케이션을 컴파일(또는 바이너리 직조)할수 있다. 만약 당신이 의존성을 aspect에 삽입한다면, 그것들의 bean정의는 AtAspectJFactoryBean를 통해 삽입하기 위한 aspect인스턴스를 얻기 위해 미세하게 변경되거나 당신은 @Configurable처럼 aspect를 간단히 주석처리하고 spring-aspects.jar aspect라이브러리를 사용할수 있다.
No comments:
Post a Comment