스프링 AOP에서 this와 target 포인트컷 표현식 구분 하시는 분?

Pro Spring 2.5에서 this 표현식에 대한 정의입니다.

the semantics of the this pointcut are such that it would match any method execution on an object whose class matches the specified expression, but how could we match any class in the com package and its subpackages? Therefore, the only allowed syntax is this(class-name),

Pro Spring 2.5에서 target 표현식에 대한 정의 입니다.

Because the target expression defines a pointcut that would match execution of any method on an object whose class matches the specified expression, we cannot use the wildcards.

뭐가 달라 보이나요??? 앞 부분에 어순이 약간 바뀐것 빼고는 도무지 차이를 알 수가 없습니다.

레퍼런스를 보죠.

this – limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type

target – limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type

레퍼런스는 그나마 좀 차이가 보입니다. this일 때는 프록시 객체고 target 일 때는 프록시를 적용할 타겟 객체가 주체가 됩니다.

즉, this 표현식은 주어진 타입에 해당하는 스프링 AOP Proxy의 조인포인트에 대응하는 표현식이고.. target 표현식은 주어진 타입에 해당하는 타겟 객체의 조인포인트에 대응하는 표현식이라는 것인데..

어차피 프록시 객체의 타입이 곧 타겟 객체의 타입과 동일하기 때문에 결과는 똑같을 것 같은데 말이죠. 차이를 모르겠습니다. 아흐.. 머리야..

마지막으로 AspectJ 문서를 보겠습니다.

this(Type or Id)
    Picks out each join point where the currently executing object (the object bound to this) is an instance of Type, or of the type of the identifier Id (which must be bound in the enclosing advice or pointcut definition). Will not match any join points from static contexts.

target(Type or Id)
    Picks out each join point where the target object (the object on which a call or field operation is applied to) is an instance of Type, or of the type of the identifier Id (which must be bound in the enclosing advice or pointcut definition). Will not match any calls, gets, or sets of static members.

this는 동적이고 target은 정적으로 타입을 지칭하는 차이밖에 없어보입니다. 흠.. 과연..

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=”applicationContext.xml”)
public class FooAspectTest {

    @Autowired
    FooBean fooBean;

    @Test
    public void isProxy(){
        assertTrue(fooBean instanceof Advised);
        fooBean.hi();
    }

}

@Aspect
public class FooAspect {

    @Pointcut(“this(org.opensprout.spring.aop.thisandtarget.FooBean)“)
    public void thisPointcut(){}

    @Before(“thisPointcut()”)
    public void say(){
        System.out.println(“this pointcut executed.”);
    }

}

스프링 AOP에서는 this를 target으로 고쳐도 똑같이 동작합니다. 콘솔에 메시지가 한 번 출력되죠. 그러나 AJDT로 실행하면, this의 경우 같은 메시지가 세 번 찍히고, target의 경우 같은 메시지가 네 번찍힙니다. 일단 두 번은 클래스 로딩이나 생성자 호출 조인포인트에서 찍혔다고 생각하고, 하나는 테스트에서 hi 호출 할 때 찍힌거고 가장 의아한건 target 표현식일 때의 마지막 한 번.

디버깅을 돌려본 결과 저 테스트에 두 번 들어가던데;; 왜 그런지 몰겠네요. 아~ 미궁이로 구나..

JDK 6.0에서 CGLib과 JDK 프록시 성능 비교

테스트 환경

JDK 1.6
CGLib 2.1_3 nodep
Spring 2.5.5

테스트 클래스

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=”springContext.xml”)
public class HelloAspectTest{

    @Autowired
    Hello goodHelloImpl;
   
    @Test
    public void createProxy() throws Exception {
        assertNotNull(goodHelloImpl);
        StopWatch stopWatch = new StopWatch();
        testProxiedMethod(10, stopWatch);
        System.out.println(“total ” + stopWatch.getTotalTimeMillis());
    }

    private void testProxiedMethod(int count, StopWatch stopWatch) {
        while(count > 0){
            stopWatch.start();
            for(int i = 0 ; i < 1000000 ; i++)
                goodHelloImpl.hi();
            stopWatch.stop();
            System.out.println(stopWatch.getLastTaskTimeMillis());
            count–;
        }
       
    }
}

CGLib을 사용할 때는 Concrete class 타입을 써도 상관없는데, JDK 프록시를 사용할 때는 인터페이스 타입을 써야 합니다. 이유는 아시죠? 설명은 패스합니다.

StopWatch 클래스는 스프링이 제공해주는건데, 위처럼 요긴하게 쓸 수 있습니다. API는 설명이 필요 없을 만큼 작명을 잘 해뒀기 때문에 그냥 읽어보시면 어떤 일을 하는지 알 수 있으실 겁니다.

스프링 설정 파일

    <context:component-scan base-package=”org.opensprout.sandbox.proxy”>
        <context:include-filter type=”annotation” expression=”org.aspectj.lang.annotation.Aspect” />
    </context:component-scan>

    <aop:aspectj-autoproxy proxy-target-class=”true” />

윗 부분에 주의해야 합니다. proxy-target-class=”true” 이 설정을 삭제하면, JDK 프록시를 사용하는 것이고, 저 설정 그대로면, CGLib을 사용하는 겁니다.

어드바이스 적용 대상 인터페이스 => 타겟 인터페이스

public interface Hello {
   
    public String hi();

}

어드바이스 적용 대상 구현체 => 타겟 클래스

@Component
public class GoodHelloImpl implements Hello {
   
    public String hi(){
        return “”;
    }

}

클래스 이름이나 메소드 이름 등에 너무 신경쓰지 말아주세요. 흑흑..ㅠ.ㅠ 대충 만든거란 말에요. 그것 보단, 이 녀석이 인터페이스를 구현하고 있기 때문에, 스프링이 프록시를 만들 때 proxy-target-class=”true” 이 설정이 없으면 기본적으로 JDK 프록시를 생성한다는 사실.. 아시죠?

자 그럼 이제부터 쇼타임.

1. CGLib 사용시

위 설정 그대로 입니다.

1250
1204
1203
1218
1204
1203
1203
1219
1218
1219
total 12141

2. JDK 프록시 사용시

스프링 설정에서  proxy-target-class=”true” 이걸 삭제합니다.

1687
1657
1656
1656
1656
1672
1672
1672
1656
1656
total 16640

3. CGLib 프록시를 서버 모드로

위 설정 그대로 사용합니다.
테스트 실행시 VM 인자에 -server 추가합니다.

813
796
797
813
797
797
812
797
797
total 8172

4. JDK 프록시를 서버 모드로

스프링 설정에서  proxy-target-class=”true” 이걸 삭제합니다.
테스트 실행시 VM 인자에 -server 추가합니다.

1078
922
922
937
906
938
906
922
937
938
total 9406

캬요~ 자바지기님께서 테스트 했을 때에 비하면 JDK 프록시 성능 많이 좋아졌죠?

Spring AOP 어디다 쓰면 좋을까?

어드바이스 종류 별로 생각해 보는게 좋을 듯 합니다. 어드바이스 특징이나 기타 자세한 설명은 생략하겠습니다. 제 블로그 어딘가에 다 있을 겁니다.ㅋ

1. Before 어드바이스
– 보안에 쓰면 좋겠다. 해당 메소드 실행 전에 인증과 권한을 체크해서, 없으면 예외를 던지도록 할 수 있겠다.
– 로깅은 어떨까.. 어떤 메소드를 실행하기 전에 해당 메소드 실행하겠다고 로그 메시지를 남기는거..

2. After returning 어드바이스
– 결과값을 확인해야 할 경우. 결과값이 특정 값이면 예외를 던지도록…
– 흠.. 매우 사용처가 난감하네. 이미 메소드 호출 한 다음이라.. 뭘 해야 한담.
– 메소드 호출 한 뒤에 로그 메시지를 남기도록 할까?

3. Throws 어드바이스
– 예외 로그 처리. 특정 예외가 발생했을 때 로그를 남길 수 있겠다.
– 예외 계층 구조를 바꿀 수 있겠다. Hibernate 예외를 스프링 예외로 변환하는 작업을 해본적이 있었지..

4. Around 어드바이스
– 트랜잭션 처리는 아마 이 녀석이 할 것 같다. 특정 메소드 실행 전에 트랜잭션 열고, 메소드 호출하고 트랜잭션을 커밋하거나 롤백해야 하니까.
– 로깅을 이걸로 할까. 그러면 Before, After, Throws에서 하려던 로깅을 이걸로 다 할 수 있으니..
– 성능측정. 스프링 레퍼런스에 StopWatch 예제가 있었던 것 같다. 개발 할 때만 만들어서 돌려보고 병목지점 발견하는데 요긴하게 쓸 수 있을 것 같기도 하다.

전반적으로 AOP는 기존 코드를 건드리지 않고 애플리케이션에 특정 행위를 추가할 수 있다는 것이 매력적인 것 같다.

Spring AOP의 로깅과 성능 측정용 Interceptor

스프링 레퍼런스 7장의 예제를 유심히 보다보면 Spring에서 자체적으로 구현해둔 Interceptor들을 볼 수 있습니다. 그 중에 몇개를 살펴 보도록 하겠습니다.

디버그 인터셉터 :: org.springframework.aop.interceptor.DebugInterceptor

성능 측정 인터셉터 :: org.springframework.aop.interceptor.PerformanceMonitorInterceptor

사용법은 ProxyFactoryBean으로 간단하게 설정하여 사용할 수 있습니다.

    <bean id=”proxy”
        class=”org.springframework.aop.framework.ProxyFactoryBean”>
        <property name=”target” ref=”service” />
        <property name=”interceptorNames”>
            <list>
                <value>global*</value>
            </list>
        </property>
    </bean>

    <bean id=”global_debug”
        class=”org.springframework.aop.interceptor.DebugInterceptor” />
    <bean id=”global_performance”
        class=”org.springframework.aop.interceptor.PerformanceMonitorInterceptor” />

target 속성에 디버깅 또는 퍼포먼스 체크를 하고 싶은 bean의 이름을 설정해 주면 됩니다. 둘 중 하나만 사용하고 싶다면 interceptorNames에서 조정을 하면 됩니다.

Spring AOP APIs

7.1. Introduction

이전 장에서 배운 것은 Spring 2.0에서의 AOP이고 이번에는 좀 더 하위 레벨의 1.2 방식을 알아보겠습니다.

Spring AOP(old) 특징

7.2. Pointcut API in Spring

Pointcut 인터페이스를 구현한 클래스들을 제공하며 이 클래스들을 사용하여 포인트컷을 만들고 재사용할 수 있습니다.

Spring AOP(old) Pointcut
Spring AOP(old) Pointcut Implementation
기선 씨네마 :: Pointcut

7.3. Advice API in Spring

모든 advice는 bean으로 등록하며 per-class와 per-instance 라이프 사이클이 존재합니다.

Spring AOP(old) Advice
기선 씨네마 :: Advice

7.4. Advisor API in Spring

advice와 pointcut을 하나씩 묶어 놓은 것입니다. DefaultPointcutAdvisor  를 사용하여 기본적으로 묶을 수 있습니다.

Spring AOP(old) Advisor

7.5. Using the ProxyFactoryBean to create AOP proxies

ProxyFactoryBean을 사용하여 AOP proxy를 만드는 방법을 설명합니다.

7.6. Concise proxy definitions

중복되는 설정이 많을 때는 parent 속성을 사용하면 좋습니다.

7.7. Creating AOP proxies programmatically with the ProxyFactory

설정이 아닌 코딩을 통해 ProxyFactory를 사용하는 방법입니다.

7.8. Manipulating advised objects

7.9. Using the “autoproxy” facility

매번 ProxyFactoryBean으로 등록하는 것이 아니라 Autoproxy를 사용하면 BeanPostProcessor가 알아서 만들어 줍니다.

Spring AOP(old) ProxyFactoryBean 불편한 점
Autoproxy
BeanNameAutoProxyCreator 사용 예

7.10 Using TargetSources



7.11. Defining new Advice types

7.12. Further resources

JPetStore를 보라고 하는군요.