http://www.aspectprogrammer.org/blogs/adrian/2006/01/typed_advice_in.html

<div class="note"> bind - 연결하다. </div>

Spring 2.0(M2기준) 에서의 타입화된 Advice#

이 글은 AspectJ개발자인 adrian이 새로운 aop스키마와 Spring2.0내 @AspectJ지원을 위해 인자를 advice로 바인딩하는 작업후 새로운 기능에 대한 설명과 사용하는 방법에 대해 알리는 글입니다.

Spring 2.0은 DTD기반과 스키마-기반 설정을 모두 지원한다. XML사용시 스키마를 사용하는 것이 DTD를 사용하는 것보다 많은 장점을 가지는 것은 개발자들 사이에 크게 알려져있다. 당신이 bean정의를 어떠한 방식(DTD-기반, 스키마-기반, 스크립트-기반....)으로 명시하더라도 모든 설정은 실행시에 결국은 같은 bean메타데이타가 된다. 그래서 서로 다른 스타일은 호환가능하고 서로 교체가 가능하도록 지정하는 것이 중요하다. AOP지원을 위해, Spring의 AOP관련 개발자는 http://www.springframework.org/schema/aop/spring-aop.xsd 스키마를 제공한다.

이것은 새로운 스키마지원을 사용하는 것처럼 보이는 골격을 갖춘 설정파일이다.

<?xml version="1.0" encoding="UTF-8"?>
<beans 
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"      
  xsi:schemaLocation="http://www.springframework.org/schema/beans   
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

           
</beans>

다양한 AOP관련 정의는 <aop:config> 요소내 둘수 있다. 다음의 설정은 테스트 묶음에서의 간단한 aspect의 단축예제를 보여주고 있다.

<aop:config>
  <aop:aspect id="beforeAdviceBindingTests" ref="testAspect">
    <aop:advice 
  kind="before"
  method="oneIntArg"
  pointcut="execution(* setAge(..)) and args(age)"
    />
  </aop:aspect>
</aop:config>

<bean id="testAspect" class="org.springframework.aop.aspectj.AdviceBindingTestAspect"/>

위 설정은 "beforeAdviceBindingTests"라고 불리는 aspect를 선언하고 있다. aspect인스턴스는 "testAspect" bean(ref="testAspect")이다. 이것은 대개 사용되는 방식으로 Spring을 설정할수있는 정규 bean이다. 구현클래스(이 경우, AdviceBindingTestAspect)는 정규 POJO이다. aspect는 "before" advice인 하나의 advice를 가진다(AspectJ의 advice의 다섯가지 종류는 before, afterReturning, afterThrowing, after, 또는 around이다. 자세한 사항은 관련 책이나 글을 참조하길 바란다.). advice몸체는 "oneIntArg"메소드에 의해 구현된다. 그리고 관련된 pointcut표현은 하나의 int인자를 가지는 "setAge"라는 이름의 메소드의 실행에 대응된다. 여기서 int형 인자는 "age"라는 파라미터에 연결된다.

이것을 요약하면, 특정시점에 대응되는 메소드가 애플리케이션 컨텍스트내 Spring bean에서 수행된다는 것이다. "oneIntArg" 메소드는 파라미터로 age를 전달하여 "testAspect" bean에서 호출될것이다.

여기서 중요한 두가지 사실을 얻었다. aspect클래스와 advice메소드는 어느 특정 인터페이스를 구현할 필요가 없다. 그리고 우리는 타입화된(typed) advice를 가진다. 타입화된 advice에 의해, advice시그너처(signature)는 필요한 파라미터를 선언하고 오직 전달된 파라미터만 얻으며 타입 정보를 가진다. advice몸체로부터 언제나 Object를 전달하는 전통적인 AOP프레임워크의 접근법과는 달리 신중하고 고르고(pick-and-choose) 필요한 것만 전달해야만 한다.

이 설정예제를 좀더 완성해보기 위해, 다음은 aspect클래스이다.

public class AdviceBindingTestAspect {

  public void oneIntArg(int age) {
     // whatever we like here, using "age" as needed
  }
}

클래스선언과 XML정의의 조합은 다음의 AspectJ aspect선언과 같은 영향을 끼친다.

public aspect AdviceBindingTestAspect {

  before(int age: execution(* setAge(..)) && args(age) {
     // whatever we like here, using "age" as needed
  }
}

그럼 이제부터는 이 글의 중요한 주제를 하나씩 훍어보자. 각각의 주제는 타입화된 advice를 위한 지원에 연결하는 방식과 그것을 사용하는 방식을 알려줄것이다.

인자 이름과 타입을 결정하기#

위 AspectJ aspect정의를 보자. advice는 int타입의 "age"파라미터를 선언하고 pointcut표현내에서 "age"가 "args"요소에 의해 연결된다. "args(age)" 표현은 실제로 두가지는 수행한다. "int"타입("age"의 타입)의 하나의 인자가 있는 join point에 대한 대응(matching)을 제한하고 "age" 파라미터에 인자값을 넣어준다.

Spring 2.0에서, 같은 pointcut표현이 있지만 "age"파라미터를 선언하는 메소드는 다른곳인 AdviceBindingTestAspect클래스에 정의된다. 자바 리플렉션(reflection)은 이 지점에 두도록 한다. 우리가 결정할수 있는 모든것은 "oneIntArg"메소드가 "int"타입의 하나의 파라미터를 가지는것이다. 우리가 파라미터 이름을 결정할수는 없다. 이 경우, advice메소드가 오직 하나의 인자를 갖고 pointcut이 오직 하나의 인자에 연결되기 때문에, int파라미터가 "age"에 일치하는것을 생각할수 있다. 하지만 여러개의 인자를 바인딩하는 것은 언제나 가능한것은 아니다. 인자 타입에 인자 이름을 맞추는것은 대응(matching) 프로세스에 중요하고 물론 advice메소드 자체에 인자를 전달한다.

Spring 2.0은 많은 수의 "ParameterNameDiscoverers"를 지원하여 이 이슈를 해결한다. ParameterNameDiscoverers는 전략적인(strategy) 인터페이스이고 다중 구현물이 ChainOfResponsibility내 중렬된다. Spring은 다음의 순서대로 인자의 이름을 알아낸다.

1. 인자 이름이 명시적으로 선언되었다면, 주어진 인자의 이름이 사용된다. 인자 이름은 두가지 방법중 하나로 명시될수 있다.

  • <: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
  kind="afterReturning"
  method="needsJoinPoint"
  pointcut="execution(* getAge())"
/>

advice method선언:

public void needsJoinPoint(JoinPoint tjp) {
   this.collaborator.needsJoinPoint(tjp.getSignature().getName());
}

적절한 JoinPoint객체을 확인하는 테스트 케이스가 advice메소드로 전달된다.

public void testNeedsJoinPoint() {
    mockCollaborator.needsJoinPoint("getAge");
    mockControl.replay();
    testBean.getAge();
    mockControl.verify();
}

메소드 실행 인자#

메소드 실행 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
  kind="afterReturning"
  method="afterGettingName"
  returning="name"
  pointcut="execution(* getName())"
/>

메소드 정의와 묶어보자.

public void afterGettingName(String name) {
   // advice body
}

"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">
  <aop:pointcut name="legacyDaoMethodExecution"
                   expression="execution(* org.xzy.dao..*.*(..))"/>
  <aop:advice 
     kind="afterThrowing"
     method="translateException"
     pointcut-ref="legacyDaoMethodExecution"
     throwing="hibEx"
  />
</aop:aspect>

그리고 첨부된 메소드 정의

public void translateException(HibernateException hibEx) {
  throw SessionFactoryUtils.convertHibernateAccessException(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
    kind="afterReturning"
    method="setAgeOnATestBean"
    pointcut="execution(* setAge(..)) and args(age) and this(bean)"
    arg-names="age,bean"
/>

이 advice는 다음의 테스트 케이스를 전달한다.

public void testOneIntAndOneObjectArgs() {
 mockCollaborator.setAgeOnATestBean(5,testBean);
 mockControl.replay();
 testBean.setAge(5);
 mockControl.verify();
}

만약 우리가 "this": "execution(* setAge(..)) 와 args(age) 와 target(bean)"대신에 "target"를 사용한다면, 테스트는 실패할것이다. 모의 협력자(mock collaborator)는 인자로 전달되기 위한 "testBean"(AOP프록시가 될)을 기대한다. 하지만 우리는 "target"를 사용하기 때문에, 프록시 target객체는 대신 전달될것이다. 이것은 다음의 테스트 케이스에 의해 설명된다.

public void testTargetBinding() {
 Advised advisedObject = (AdvisedtestBean;
 TestBean target = (TestBeanadvisedObject.getTargetSource().getTarget();
 mockCollaborator.setAgeOnATestBeanTarget(5,target);
 mockControl.replay();
 testBean.setAge(5);
 mockControl.verify();
}

주석(Annotations)#

AspectJ pointcut언어는 주석을 매치하고 바인딩하기 위한 풍부한 지원을 제공한다. Spring애플리케이션을 위한, 두개의 가장 유용한 pointcut지시어가 아마도 "@annotation" 과 "@within" 일것이다.

"@annotation"은 join point의 대상(subject)이 주어진 타입의 주석을 가질때 대응된다. Spring을 위해, 이것은 충고된(advised) 메소드가 주어진 타입의 주석을 가지는 것을 의미한다. 예를 들면, advice는 ..

<aop:advice
    kind="before"
    method="beforeTxMethodExecution"
    pointcut="@annotation(tx)"
/>

"beforeTxMethodExecution" 메소드("tx"가 대응할 주석의 타입을 결정하기 위해)의 시그너처와 묶일때

public void beforeTxMethodExecution(Transactional tx) {
  if (tx.readOnly()) { 
    ...
}

"@Transactional" 주석을 가지는 bean메소드의 실행에 대응될것이다. Spring이 오직 실행 join point에 무조건 제한되는것을 기억하라. 전체적인 AspectJ언어를 사용할때 일치하는 pointcut은 "execution(* *(..)) && @annotation(tx)"가 될것이다.

"@within"은 주어진 주석을 가지는 타입내 join point와 대응된다. 그래서 타입내 bean메소드의 수행에 대응하는 것은 우리가 작성할수 있는 "@Transactional"를 가진다.

<aop:advice
    kind="before"
    method="beforeTxMethodExecution"
    pointcut="@within(tx)"
/>

@AspectJ 스타일#

모든 예제에서, 나는 당신에게 aspect정의의 XML스키마 형태를 사용한것을 보였다. Spring 2.0은 "@AspectJ 스타일"을 사용하여 작성된 aspect를 사용할수 있다. 이것은 이 볼로그의 범위를 넘어서지만, 여기에 이것이 작동하는 것의 간단한 예제가 있다.

당신이 할 필요가 있는 첫번째는 애플리케이션 컨텍스트내 @AspectJ aspect로부터 자동-프록시를 가능하게 하는것이다. 이것은 AOP를 위해 새로운 스키마 지원을 사용할때 필요하다.

<aop:aspectj-autoproxy/>

만약 우리가 애플리케이션 컨텍스트내 실질적인 @AspectJ aspect(AspectJ주석을 사용하여 주석처리된)인 bean을 정의한다면, Sprng은 AOP프록시를 설정하기 위해 bean을 자동으로 사용할것이다. @AspectJ aspect로 작성된 전통적인 메소드 실행 예제는 다음과 같을것이다.

@Aspect
AnnotationDrivenTransactionManager {

    @Before("execution(* *(..)) && @annotation(tx)")
  public void beforeTxMethodExecution(Transactional tx) {
     // ...
    }
}

이 aspect를 사용하기 위해, 우리는 다음처럼 정의를 추가할것이다.

<bean class="...AnnotationDrivenTransactionManager" >

물론, 일반적인 모든 의존성삽입 기능은 aspect를 설정하기 위해 사용가능하다.

aspect정의에서 이 스타일을 사용하는 가장 멋진 장점은, 당신이 설정에서 "aspectj-autoproxy"요소를 제거하여 간단히 AspectJ 직조(weaving)하기 위한 Spring AOP프록시를 사용하여 전환할수 있고 대신 AspectJ를 사용하여 애플리케이션을 컴파일(또는 바이너리 직조)할수 있다. 만약 당신이 의존성을 aspect에 삽입한다면, 그것들의 bean정의는 AtAspectJFactoryBean를 통해 삽입하기 위한 aspect인스턴스를 얻기 위해 미세하게 변경되거나 당신은 @Configurable처럼 aspect를 간단히 주석처리하고 spring-aspects.jar aspect라이브러리를 사용할수 있다.

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-7) was last changed on 06-Apr-2006 09:45 by 이동국