[Spring5입문] 스프링 AOP

2021. 8. 11. 14:10CSE/Spring

- 스프링 AOP를 이용하는 방법은 다음과 같다.

  • Aspect로 사용할 클래스에 @Aspect 애노테이션을 붙인다.
  • @Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut을 정의한다.
  • 공통 기능을 구현한 메서드에 @Around 애노테이션을 적용한다.

- 개발자는 공통 기능을 제공하는 Aspect 구현 클래스를 만들고 자바 설정을 이용해서 Aspect를 어디에 적용할지 설정하면 된다.

- 프록시는 스프링 프레임워크가 알아서 만들어준다.

@Aspect
public class ExeTimeAspect {

    //@Pointcut은 공통 기능을 적용할 대상을 설정한다.
    @Pointcut("execution(public * chap07..*(..))")
    private void publicTarget(){

    }

    //@Around 애노테이션은 Around Advice를 설정한다.
    //publicTarget() 메서드에 정의한 Pointcut에 공통기능을 적용한다
    @Around("publicTarget()")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable{
    	//ProceedingJoinPoint 파라미터는 프록시 대상 객체의 메서드를 호출할 때 사용한다.
        
        long start = System.nanoTime(); //joinPoint.proceed() 전에 공통 기능 코드 위치
        
        try{
            Object result = joinPoint.proceed(); //프록시 대상 객체 메서드 실행
            return result;
        }finally{
        	//joinPoint.proceed() 후에 공통 기능 코드 위치
            long finish = System.nanoTime();
            Signature sig = joinPoint.getSignature();
            System.out.printf("%s.%s(%s) 실행시간 : %d ns\n",
                    joinPoint.getTarget().getClass().getSimpleName(),
                    sig.getName(), Arrays.toString(joinPoint.getArgs()),
                    finish-start);
        }
    }
}

- 위 코드는 메서드 실행 전/후 (Around Advice)에 사용할 공통 기능(Aspect)이다

== 위 코드는 Around Advice에서 사용할 Aspect이다.

 

★ 메서드 시그니처

- 자바에서 메서드 이름과 파라미터를 합쳐서 메서드 시그니처라고 한다.
- 메서드 이름이나 파라미터 타입, 개수가 다르면 시그니처가 다르다

- 이 때, 리턴 타입이나 익셉션 타입은 시그니처에 포함되지 않는다.

 

//스프링 설정 파일
@Configuration
@EnableAspectJAutoProxy
public class AppCtx {

    @Bean
    public ExeTimeAspect exeTimeAspect(){
        return new ExeTimeAspect();
    }

    @Bean
    public Calculator calculator(){
        return new RecCalculator();
    }
}

- @Aspect 애노테이션을 붙인 클래스를 공통 기능으로 적용하려면 @EnableAspectJAutoProxy 애노테이션을 설정 클래스에 붙여야 한다.

=> 스프링은 @Aspect 애노테이션이 붙은 빈 객체를 찾아서 빈 객체의 @Pointcut 설정과 @Around 설정을 사용한다.

 

public class MainAspect {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);

        Calculator cal = ctx.getBean(Calculator.class);
        long fiveFact = cal.factorial(5);
        System.out.println("cal.factorial(5) = " + fiveFact);
        System.out.println(cal.getClass().getName());
        ctx.close();
    }
}

//RecCalculator.factorial([5]) 실행 시간 : 50201 ns
//cal.factorial(5) = 120
//com.sun.proxy.$Proxy17

- Calculator 타입이 RecCalculator 클래스가 아니고 $Proxy17이다. (스프링이 생성한 프록시 타입)

=> 실제 cal.factorial(5) 코드를 호출할 때 실행되는 과정이 다음과 같기 때문

 

* ProceedingJoinPoint 인터페이스는 다음 메서드를 제공한다.

  • Signature getSignature() : 호출되는 메서드에 대한 정보를 구한다
  • Object getTarget() : 대상 객체를 구한다
  • Object[] getArgs() : 파라미터 목록을 구한다

* org.aspectj.lang.Signauture 인터페이스는 다음 메서드를 제공한다.

  • String getName() : 호출되는 메서드의 이름을 구한다
  • String toLongString() : 호출되는 메서드를 완전하게 표현한 문장을 구한다 (메서드의 리턴 타입, 파라미터 타입이 모두 표시됨)
  • String toShortString() : 호출되는 메서드를 축약해서 표현한 문장을 구한다 (메서드의 이름만을 구한다)