티스토리 뷰
- 스프링은 프록시를 이용해 메소드 호출 시점에 Aspect를 적용한다
- 스프링에서 가장 널리 사용되는 것은 Around Advice이다.
Around Advice는 대상 객체의 메소드 실행 전, 후 또는 익셉션 발생 시점에 Advice를 적용한다.
스프링 AOP 구현
스프링에서 지원하는 AOP 기능들로 간단하게 AOP가 가능하다.
- Aspect로 사용할 클래스에 @Aspect 애노테이션을 붙인다.
=> 공통 기능으로 사용할 기능을 해당 클래스에 정의하면 된다.
- @Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut을 정의한다.
=> @Aspect로 정의한 공통 기능을 적용할 지점들을 정의한다.
- 공통 기능을 구현한 메소드에 @Around 애노테이션을 붙인다.
=> 즉 @Aspect가 붙은 클래스에 @Around 애노테이션을 붙인 메소드가 실제 공통 기능으로서 실행될 메소드인 것이다.
우선 Calculator 인터페이스와, 이 인터페이스를 구현하는 RecCalculator 클래스가 있다.
package chap07;
public interface Calculator
{
public long factorial(long num);
}
package chap07;
public class RecCalculator implements Calculator
{
@Override
public long factorial(long num)
{
if(num == 0) return 1;
else return num * factorial(num-1);
}
}
그리고 Aspect로 사용할 클래스인 ExeTimeAspect 클래스가 있다.
package aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.util.Arrays;
// Aspect 로 사용할 클래스
@Aspect
public class ExeTimeAspect
{
// 공통 기능을 적용할 Pointcut
@Pointcut("execution(public * chap07..*(..))")
private void publicTarget() {}
// 공통 기능을 구현한 메소드에 @Around
// @Pointcut 에 이 메소드를 적용한다
// 즉 chap07 패키지나 그 하위 패키지에 속한 빈 객체의 public 메서드에 @Around가 붙은 measure 메소드를 적용한다
// 이 메소드가 실행되는 시점은 factorial() 메소드가 실행될때
@Around("publicTarget()")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable
{
long start = System.nanoTime(); // 시작 시간
try
{
// ProceedingJoinPoint.proceed() 메소드는 실제 대상 객체의 메소드 호출함
Object result = joinPoint.proceed(); // RecCalculator.factorial()
return result;
}
finally
{
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));
}
}
}
이 클래스가 공통 기능으로서 수행될 메소드가 위치하는 클래스이다.
공통 기능으로서 수행될 메소드인 measure() 메소드에는 @Around 애노테이션을 붙이고,
이 메소드가 적용되는 지점들(pointcut)을 정의하는 메소드에 @Pointcut 애노테이션을 붙여 정의한다.
다음은 스프링 설정 클래스인 AppCtx 클래스다.
@Configuration
@EnableAspectJAutoProxy
public class AppCtx
{
@Bean
public ExeTimeAspect exeTimeAspect()
{
return new ExeTimeAspect();
}
// ExeTimeAspect 의 measure 메소드가 적용됨
@Bean
public Calculator calculator()
{
return new RecCalculator();
}
}
@Aspect 애노테이션을 붙인 클래스를 공통 기능으로 적용하려면 @EnableAspectJAutoProxy 애노테이션을 설정 클래스에 붙여야 한다.
스프링은 @Aspect이 붙은 빈 객체 (여기서는 exeTimeAspect 객체)를 찾아서 @Pointcut 설정과 @Around 설정을 사용한다.
마지막으로 메인 클래스이다.
package main;
import chap07.Calculator;
import config.AppCtx;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainAspect
{
public static void main(String[] args)
{
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppCtx.class);
// cal은 Calculator 객체가 아닌 Spring에서 만든 $Proxy 객체
// $Proxy는 Calculator 인터페이스를 상속받음
Calculator cal = ctx.getBean("calculator", Calculator.class);
long fiveFact = cal.factorial(5);
System.out.println("cal.factorial(5) = " + fiveFact);
System.out.println(cal.getClass().getName());
ctx.close();
}
}
실행 결과:
실행 결과를 보면 ExeTimeAspect 클래스의 measure 메소드에서 구현한 실행 시간을 구하는 부분이 잘 출력됐다.
또한 3번째 줄을 보면 com.sun.proxy.$Proxy17이 출력 됐는데 이건 cal.getClass().getName() 의 결과다.
즉 ctx.getBean("calculator", Calculator.class)로 빈 객체를 갖고 왔을때 받은 빈 객체는 RecCalculator 클래스가 아니라 스프링에서 만든 Proxy17 이라는 이름의 프록시 객체였다.
실행 순서를 생각해보자.
1. 메인 함수의 Calculator cal = ctx.getBean("calculator", Calculator.class)
Calculator cal = ctx.getBean("calculator", Calculator.class);
여기서 빈 객체를 받아와야 하니 설정 클래스인 AppCtx 클래스로 갈것이다.
AppCtx 클래스에는 @EnableAspectJAutoProxy이 있으므로 @Aspect이 붙은 빈 객체 (여기서는 exeTimeAspect 객체)를 찾아서 @Pointcut 설정과 @Around 설정을 사용한다.
즉 이 시점에 스프링이 설정에 따라 프록시 객체를 만들고 cal은 ExeTimeAspect 프록시 객체이다.
2. 메인 함수의 cal.factorial(5)
long fiveFact = cal.factorial(5);
cal은 스프링이 생성한 ExeTimeAspect 프록시 객체.
factorial() 메소드는 @Pointcut에서 설정한것과 일치하므로 (chap07 패키지 아래 public 메소드) @Around 가 붙은 measure() 메소드가 실행된다.
3. measure 메소드의 try~finally
measure 메소드가 실행되고 try 블록의 joinPoint.proceed() 가 실행된다.
즉 RecCalculator.factorial(5)가 실행된다.
measure 메소드의 파라미터 ProceedingJoinPoint joinPoint에 메인 함수의 cal.factorial(5)에서 전달된 파라미터 5도 저장되어 있다.
return문 전에 finally 블록이 실행되 결과를 출력하고 try 문의 return 문이 실행된다.
일단 이해한 바를 정리해봤는데 내부적으로 정확히 이렇게 실행되는지는 아직 잘 모르겠다.
더 알게 되는대로 추가.
출처 : 스프링5 프로그래밍 입문 (최범균 저)
'Web' 카테고리의 다른 글
Ch07. Advice 적용 순서, @Order, @Around의 Pointcut 설정 (0) | 2022.06.28 |
---|---|
Ch07. ProceedingJoinPoint, 프록시 생성 방식 (0) | 2022.06.28 |
Ch07. AOP 프로그래밍 용어, Around Advice (0) | 2022.06.27 |
Ch07. AOP 프로그래밍이란 (0) | 2022.06.25 |
Ch06. 빈 라이프 사이클, 싱글톤이 아닌 빈 객체 (0) | 2022.06.25 |
- Total
- Today
- Yesterday
- priority queue
- CSS
- Kruskal
- 조합
- DP
- 이분탐색
- Dijkstra
- dfs
- Stack
- C++
- graph
- Python
- Spring
- 자료구조
- C
- recursion
- binary search
- two pointer
- greedy
- back tracking
- permutation
- db
- Implementation
- Tree
- 재귀
- Brute Force
- MVC
- BFS
- floyd warshall
- Unity
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |