Object getTarget() : 클라이언트가 호출한 비즈니스 메소드를 포함하는 비즈니스 객체 리턴
Object[] getArgs() : 클라이언트가 메소드를 호출할 때 넘겨준 인자 목록을 Object 배열로 리턴
public class BeforeAdvice {
public void beforeLog(JoinPoint jp) {
String method = jp.getSignature().getName();
Object[] args = jp.getArgs(); System.out.println(생략...);
}
}
Advice Annotation
AOP는 Advice를 스프링 컨테이너에서 설정해주지 않아도 간단한 어노테이션을 이용하여 Advice를 주입하는 것이 가능하다.
아래와 같이 구현할 수 있다.
@Service @Aspect
public void LogAdvice(){
@Pointcut("execution(생략...)");
public void allPointcut(){};
@before("allPointcut")
public void printLog(){}
}
여기서 포인트컷은 아래와 같다.
@Pointcut("execution(생략...)");
public void allPointcut(){};
그리고 어드바이스 메소드는 아래와 같다.
@before("allPointcut")
public void printLog(){}
위와 같이 어드바이스 메소드가 결합될 포인트컷을 참조해야 한다.
AOP 기능에서 가장 중요한 Aspect 설정은 @Aspect 어노테이션을 사용하여 설정한다.
Aspect를 설정하기 위해서는 반드시 포인트컷+어드바이스 결합이 필요하다.
Advice에서의 5가지 종류 중 After-returning과 After-Throwing 어드바이스는 특이하게 포인트컷 지정을 pointcut 속성을 사용하여 포인트컷을 참조한다.
그 이유는 비즈니스 메소드 수행 결과를 받아내기 위해 바인드 변수를 지정해야 하기 때문이다.
// AfterReturning은 returning 바인드 변수를 통해 비즈니스 로직의 결과 값을 받을 수 있다.
@AfterReturning(pointcut="getPointcut()", returning="returnObj")
// AfterThrowing은 throwing 바인드 변수를 통해 예외 처리 객체의 값을 받을 수 있다.
@AfterThrowing(pointcut="getPointcut()", throwing="exceptObj")
독립된 클래스로 포인트컷 분리
어노테이션으로 Aspect를 설정할 때 가장 큰 문제점은 어드바이스 클래스마다 포인트컷 설정이 포함되면서, 비슷하거 같은 포인트컷이 반복 선언되는 재사용 불가의 문제가 발생한다.
그래서 스프링은 이런 문제를 해결하고자 포인트컷을 아래와 같이 외부에 독립된 클래스에 따로 설정한다.
// 포인트컷을 독립적으로 관리하는 클래스를 설정.
@Aspect
public class PointCommon {
@Pointcut("execution(생략...)")
public void allPointcut(){}
@Pointcut("execution(생략...)")
public void allPointcut(){}
}
// 공통적으로 설정한 PointcutCommon의을 참조하여 포인트컷을 사용할 수 있다.
@Service
@Aspect
public class BeforeAdvice {
@Before("PointcutCommon.allPointcut()")
public void beforeLog(){//생략...}
}
이런식으로 독립적으로 관리하는 것이 포인트컷을 사용하는데 있어 더욱 효율적이고 유지보수에도 용이하다.
지시자(PCD, AspectJ pointcut designators)별 구현 방법
먼저 아래와 같은 코드가 있다고 해보자
@Component
public class AppRuner implements ApplicationRunner {
@Autowired
EventService eventService;
@Override
public void run(ApplicationArguments args) throws Exception {
eventService.created();
eventService.operation();
eventService.deleted();
}
}
@Component
public class SimpleServiceEvent implements EventService {
@Override
public void created() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("created");
}
@Override
public void operation() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("operation");
}
@Override
public void deleted() {
System.out.println("deleted");
}
}
이제 위 코드에 3가지 방법으로 AOP를 구현해 보겠다.
execution expression 기반
// Aspect 정의
@Component
@Aspect
public class PerfAspect {
// Advice 정의 (Around 사용)
// Point Cut 표현식
// (com.example.demo 밑에 있는 모든 클래스 중 EventService 안에 들어있는 모든 메쏘드에 이 행위를 적용하라.)
@Around("execution(* com.example..*.EventService.*(..))")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
Annotation 기반
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PerfLogging {
}
@Component
@Aspect
public class PerfAspect {
@Around("@annotation(PerfLogging)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
아래와 같이 적용시킬 메소드에 @PerfLogging 을 붙여주면 된다.
@Component
public class SimpleServiceEvent implements EventService {
@PerfLogging
@Override
public void created() {
...
}
@PerfLogging
@Override
public void operation() {
...
}
@Override
public void deleted() {
...
}
}
위의 코드를 실행시키면 @PerfLogging 가 붙은 created(), operation() 메소드만 AOP가 적용된 것을 확인할 수 있다.
특정 bean 기반
// Aspect 정의
@Component
@Aspect
public class PerfAspect {
// 빈이 가지고있는 모든 퍼블릭 메쏘드
@Around("bean(simpleServiceEvent)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
위와 같이 빈으로 등록된 simpleServiceEvent 내 모든 public 메소드에다가 적용시킬 수도 있다.