6.4. Choosing which AOP declaration style to use

6.4.1. Spring AOP or full AspectJ?

Spring AOP는 별도의 컴파일러나 위버가 필요 없으며 AspectJ 보다 단순합니다. 하지만 컨테이너에 의해 관리되는 bean에만 advice를 적용할 수 있고 적용되는 joinpoint가 메소드 실행 시점 뿐 입니다.

도메인 객체 또는 컨테이너에 의해 관리되지 않는 객체들에 Advice를 적용해야 하거나 더 다양한 joinpoint가 필요하다면 AspectJ를 사용하는 것이 좋겠습니다.

6.4.2. @AspectJ or XML for Spring AOP?

XML 기반의 Spring AOP는 JDK 버젼이랑 관계 없이 사용할 수 있다는 장점이 있으며 설정 파일을 보고 aspect가 적용된 상태를 파악할 수 있습니다. 하지만 필요한 정보를 두 곳에 나눠 놓는 다는 점에서 DRY(Don’t Repeat Youeself) 원칙을 어긴다고 볼 수 있습니다. @AspectJ 에서는 && || ! 를 사용하여 포인트컷 끼리의 연산을 할 수가 있었는데 그걸 못합니다.

5.0 미만의 JDK를 사용해야 한다면 스키마 기반 Spring AOP를 사용하시고(유일한 선택 사항이죠.) 5.0 이상일 때 단순한 설정(예를 들어 선언적 트랙잭션 관리 같은 것) 이외에 애스팩트가 필요하다면 @AspectJ 를 사용하는게 좋겠습니다.

예제 만들면서 써보니까 전 @AspectJ가 훨씬 편하더군요.

Schema 기반 Introduction

Introduction 예제 와 동일한 예제입니다. <aop:declare-parents> 를 사용하였다는 것만 다르죠. 흐흣;;

새로 추가할 메소드를 가진 인터페이스와 그것을 구현한 클래스르 만듭니다.

public interface TicketTracked {
    void incrementTicketCount();
}

public class TicketTrackedImpl implements TicketTracked {
    static int count = 0;
    public void incrementTicketCount() {
        System.out.println(“표 ” + (++count) + ” 장 팔았다.”);
    }
}

그리고 this()를 사용하여 포인트컷을 만들어 줍니다.

<aop:pointcut id=”countTicket”
            expression=”execution(* sell*(..)) and this(ticketTracked)”/>

다음 이 포인트 컷을 사용할 introduction을 설정해 줍니다. 이 설정은 <aop:aspect> 바로 아래에 있어야 합니다. 안그럼 에러나 나더군요~

<aop:declare-parents
                types-matching=”aop.newStyle.domain.KeesunCinema”
                implement-interface=”aop.newStyle.aspect.TicketTracked”
                default-impl=”aop.newStyle.aspect.TicketTrackedImpl” />
<aop:after method=”ticketTtrack” pointcut-ref=”countTicket”/>

그리고 테스트를 해보면 원하는 결과를 확인할 수 있습니다.

    @Test
    public void sellTicket() {
        cinema.sellTicket(movie, new Date());
    }

어서 오세요. 무엇을 도와드릴까요?
어서 오세요. 무엇을 도와드릴까요?
왔어? 영화 뭐 볼껀데?
하이 공공의적보려고?
표 1 장 팔았다.
감사합니다. 공공의적을 구매 하셨습니다.
쌩큐  공공의적잘봐!

Schema 기반 Advice parameters

Advice parameters 여기서 살펴봤던 것과 거의 동일합니다. args() 표현식을 사용하여 포인트컷을 정의합니다.

<aop:pointcut id=”sellTicket2″ expression=”execution(* sell*(..)) and args(movie,..)”/>

movie라는 이름의 파라미터를 받는 메소드의 조인포인트를 가리키게 됩니다. 이걸 받아서 처리할 어드바이스를 만듭니다.

    public void veryWelcome(Movie movie){
        System.out.println(“하이 ” + movie.getName() + “보려고?”);
    }

xml 파일에 설정해 줍니다.

<aop:aspect id=”cinema” ref=”aspect”>
            <aop:before method=”welcome” pointcut-ref=”sellTicket” />
            <aop:before method=”welcome” pointcut-ref=”sellTicket” />
            <aop:after-returning method=”afterSellTicket” pointcut-ref=”sellTicket” returning=”ticket”/>
            <aop:around method=”aroundSellTicket” pointcut-ref=”sellTicket”/>
            <aop:before method=”veryWelcome” pointcut-ref=”sellTicket2″ arg-names=”movie”/>
</aop:aspect>

args-names에 넣어준 값 movie는 어드바이스 역할을 하는 메소드에서 받는 메개변수 이름입니다. 테스트를 실행하면 원하던 결과를 확인할 수 있습니다.

    @Test
    public void sellTicket() {
        cinema.sellTicket(movie, new Date());
    }

어서 오세요. 무엇을 도와드릴까요?
어서 오세요. 무엇을 도와드릴까요?
왔어? 영화 뭐 볼껀데?
하이 공공의적보려고?
감사합니다. 공공의적을 구매 하셨습니다.
쌩큐  공공의적잘봐!

before중에서 가장 마지막이고 around에서 pjp.proceed 이전에 수행하는 작업을 before로 보면 예상할 수 있는 위치에 수행된 걸 확인할 수 있습니다.

어드바이스 예제

around 어드바이스 역할을 메소드를 만듭니다. @Around 어드바이스 예제 의 예제와 거의 동일합니다.

메소드의 첫번째 인자로 ProceedingJoinPoint가 와야 하며 throws Throwable을 붙여줍니다.

    public Object aroundSellTicket(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println(“왔어? 영화 뭐 볼껀데?”);
        Ticket ticket = (Ticket) pjp.proceed();
        System.out.println(“쌩큐  ” + ticket.getMovie().getName() + “잘봐!”);
        return ticket;
    }

xml 설정 파일에 aound 어드바이스를 설정해 줍니다.

<aop:aspect id=”cinema” ref=”aspect”>
            <aop:before method=”welcome” pointcut-ref=”sellTicket” />
            <aop:before method=”welcome” pointcut-ref=”checkTicket” />
            <aop:after-returning method=”afterSellTicket” pointcut-ref=”sellTicket” returning=”ticket”/>
            <aop:around method=”aroundSellTicket” pointcut-ref=”sellTicket”/>
</aop:aspect>

테스트를 실행합니다.

    @Test
    public void sellTicket() {
        cinema.sellTicket(movie, new Date());
    }

어서 오세요. 무엇을 도와드릴까요?
왔어? 영화 뭐 볼껀데?
감사합니다. 공공의적을 구매 하셨습니다.
쌩큐  공공의적잘봐!

이 전에 사용했던 예제의 어드바이스가 같이 적용이 됐기 때문에 결과가 저러헤게 나왔습니다. 어드바이스가 적용되는 순서는 Advice ordering 여기서도 살펴봤지만 간단하게 aspect에 등록되어 있는 순이라고 생각해도 될 것 같습니다.

어드바이스에서 리턴값 받아오기

Aspect 역할을 할 클래스(이름을 Aspect로 했습니다. 꼭 그래야 하는건 아니죠;;)에 After Returning 때 weaving 될 어드바이스를 일반 메소드로 정의합니다.

public class Aspect {

    public void welcome(){
        System.out.println(“어서 오세요. 무엇을 도와드릴까요?”);
    }

    public void confirm(){
        System.out.println(“확인하기”);
    }

    public void afterSellTicket(Ticket ticket){
        System.out.println(“감사합니다. ” + ticket.getMovie().getName() + “을 구매 하셨습니다.”);
    }
}

그리고 설정파일에 어드바이스를 등록합니다.

<aop:aspect id=”cinema” ref=”aspect”>
            <aop:before method=”welcome” pointcut-ref=”sellTicket” />
            <aop:before method=”welcome” pointcut-ref=”checkTicket” />
            <aop:after-returning method=”afterSellTicket” pointcut-ref=”sellTicket” returning=”ticket“/>
</aop:aspect>

@AfterReturning 어드바이스 만들기 이것과 거의 동일한 예제입니다. 결과는 무난히 리턴값을 가져온 것을 확인할 수 있습니다.

    @Test
    public void sellTicket() {
        cinema.sellTicket(movie, new Date());
    }

어서 오세요. 무엇을 도와드릴까요?
감사합니다. 공공의적을 구매 하셨습니다.