Aspect Oriented Programming
메소드가 1000개가 있다고해보자. 이 때, 누군가가 나타나서 이 메소드들의 호출시간을 알아오라고 시킨다고 하자.
public Long join(Member member) {
long start = System.currentTimeMillis();
try {
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("join " + timeMs + "ms");
}
}
이런식으로 try finally 문을 이용해서 시작할 때 시간, 끝날 때 시간을 측정해서 값을 빼주는 방식을 떠올릴것이다.
근데 이러면 1000번을 다 해줘야하고 몇가지 문제점이 생긴다.
시간측정로직은 공통관심사항(Cross-cutting Concern)이지 핵심관심사항(Core Concern)이 아닌데 이 둘이 섞이게 되면서 코드 가독성도 떨어지고 유지보수도 힘들어진다.
또한, 시간측정 로직을 별도의 로직으로 만들기도 어렵고, 앞서 언급한것처럼 메소드마다 다 해줘야한다.
이런 상황을 해결하고자 나온것이 AOP이다.
package com.example.hellospring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
public class TimeTraceAop {
@Around("execution(* com.example.hellospring..*(..)) && !target(com.example.hellospring.SpringConfig)")
// @Around("execution(* com.example.hellospring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("START: " + joinPoint.toString());
try {
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString()+ " " + timeMs +
"ms");
}
}
}
@Bean
public TimeTraceAop timeTraceAop () {
return new TimeTraceAop();
}
AOP라는 패키지를 만들고, 그 안에 AOP를 구현해서 컨테이너에 올려주면 된다.
어라운드 어노테이션은 이 aop가 어디까지 적용되는지 지정해준다.
@Around("execution(* com.example.hellospring..*(..)) && !target(com.example.hellospring.SpringConfig)")
@Around("execution(* com.example.hellospring..*(..))")
문제는 2번째 방식으로 around를 적용하면 오류가 난다는점이다.
이 글을 참고하자.
보이는것처럼 시간이 잘 측정된다!
AOP가 적용되는 부분에 관해서는 Bean을 Proxy Bean을 만든다. Spring은 시간을 측정하도록 코드가 조작된 Proxy를 호출하고, joinPoint를 통해 진짜 Bean을 호출하는 방식이다.
자세한 내용은 따로 정리하겠다.