Chap 7. AOP 프로그래밍
프로젝트 준비
pom.xml에 aspectjeaver 의존 추가를 한다.
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
asepectjwearer 모듈은 AOP 설정시 필요한 애노테이션을 제공한다.
프록시
프록시는 대행자 역할으로 생각할 수 있다.
실행흐름 상 ExeTimeCalculator을 통과 후 ImpeCalculator 에 접근 할 수 있다.
이 때 ImpeCalculator 클래스의 코드 변경 없이 추가기능을 생성했다.
핵심 기능의 실행은 다른 객체에게 위임하고, 부가적인 기능을 제공하는 객체를 프록시(proxy)라고 한다. 다시 말해 프록시는 핵심 기능 구현을 하지 않는다.
AOP
AOP는 여러 객체에 공통으로 적용 할 수 있는 기능을 분리하여 재사용성을 높이는 프로그램 기법이다. 공통 기능 구현과 핵심 기능 구현을 분리하는 것이 AOP의 핵심이다.
AOP 핵심기능은 코드의 재사용성, 유연성 보장, 핵심 기능 수정하지 않는다.
핵심 기능에 공통 기능을 삽입하는 방법 3가지는 다음과 같다.
*ApectJ와 같이 AOP 전용 도구를 사용하여 적용
- 컴파일 시점에 코드에 공통 기능 삽입
- 클래스 로딩 시점에 바이트 코드에 공통 기능을 삽입하는 방법
*SpringFramework AOP(proxy) - 실제 객체의 기능을 실행하기 전/후에 공통 기능 호출
- 런타임에 프록시 객체를 생성해서 공통 기능을 삽입하는 방법
AOP에서 공통 기능을 Aspect라고 하고 주요용어 4가지는 다음과 같다.
- Advice : 핵심 로직에 적용여부 정의, 기능 적용하는것을 정의한다.
- Joinpoint (추상적) : Advice 적용시점 의미한다.
- pointcut : JoinPoint 부분 집합으로서 실제 Advice 적용되는 Joinpoint 이다.
- Weaving (추상적) : Advice 핵심 로직 코드에 적용하는 것이다. / 접합
- Aspect : 여러 객체에 공통으로 적용되는 기능이다.
Advice 종류 중 Around Advice 를 이용하여 사용자가 지점한 시점에 원하는 기능을 넣을 수 있어 가장 널리 사용된다.
스프링 AOP 구현
스프링 AOP를 이용한 공통 기능 구현과 적용 방법의 절차는 다음과 같다.
1. Aspect로 사용할 클래스에 @Aspect 애노테이션을 붙인다.
2. @Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut을 정의한다.
3. 공통 기능을 구현한 메서드에 @Around 애노테이션을 적용한다.
다음 코드는 실행 시간을 측정하는 Aspect를 구현한 코드이다. 메서드 실행 전후에 사용할 공통기능으로 해석 할 수 있다.
package aspect;
import java.util.Arrays;
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;
@Aspect
public class ExeTimeAspect {
@Pointcut("execution (public * chap07 ..*(..))")//포인트 컷 지정 - 누가(Who)
private void publicTarget() {}
@Around("publicTarget()")// 어떻게 (How)
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable
{
long start = System.nanoTime();
try
{
Object result = joinPoint.proceed();
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));
}
}
}
@Pointcut은 공통 기능을 적용할 대상을 설정한다. (Who)
위 코드에 설정 범위는 chap07의 패키지와 그 하위 패키지에 위치한 public 타입 메서드를 설정하는 것이다.
@Around 애노테이션은 Around Advice를 설정한다. (How)
publicTarget()는 시그니처로서 함수명과 매개변수를 합쳐서 시그니처라고 한다.
ProceedingJoinPoint 타입 파라미터는 실제 대상 객체의 메서드를 호출한다.
AOP기능은 빈에 등록된 것만 적용된다. @Aspect 애노테이션을 붙인 클래스를 공통 기능으로 적용하려면 @EnableAspectJAutoProxy 애노테이션을 설정할 클래스(@Configration)에 붙여야 한다.
Around Advice에서 사용할 공통 기능 메서드는 파라미터로 전달 받은 ProceedingJoinPoint의 proceed() 메서드만 호출하면 된다.
전달 인자들은 다음과 같은 정보를 제공한다.
- Signature getSignature() : 호출되는 메서드에 대한 정보를 구한다.
- Object getTarge() : 대상 객체를 구한다.
- Object[] getArgs() : 파라미터 목록을 구한다.
@EnableAspectJAutoProxy(proxyTargeClass = true)로 지정하면 인터페이스가 아닌 자바 클래스를 상속받아 프록시를 생성한다.
execution 명시자 표현식
@Pointcut("execution (public * chap07 ..*(..))")//포인트 컷 지정 - 누가(Who)
private void publicTarget() {}
execution 명시자는 Advice를 적용할 메서드를 지정할 때 사용한다. 기본 형식은 다음과 같다.
execution(수식어패턴?리턴타입패턴 클래스이름패턴?메서드 이름패턴(파라미터패턴)) |
수식어패턴(접근 제어자)은 생략 가능하다. 스프링 AOP에선 public만 사용할 수 있어서 사실상 public만 있을 수 있다.
각 패턴은 '*'을 이용하여 모든 값을 표현 가능하다. 또한 '..'를 이용하여 0개 이상을 나타낸다
다음은 명시자 표현식의 예시이다. (/로 끊어서 해석)
* execution(public void set * (..)) => execution(public / void / set * / (..))
- 리턴 타입이 void, 메서드 이름이 set, 파라미터가 0개 이상인 메서드 호출한다.
* execution(Long chap07.Calculator.factorial(..)) => execution(Long / chap07.Calculator.factorial / (..))
- 리턴 타입이 Long 인 Calculator 타입의 factorial() 메서드 호출
* execution(* get*(*))
- 이름이 get으로 시작하고 파라미터가 한 개인 메서드 호출
* execution(* get*(*, *))
- 이름이 get으로 시작하고 파라미터가 두 개인 메서드 호출