AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 한다. 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점을 나누어 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다.
(* 모듈화: 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것)
소스 코드 상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는데, 이것을 흩어진 관심사(Crosscutting Concerns)라고 부른다.

위와 같이 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지다.

프록시 객체는 내부에 실제 대상 객체(target)를 참조하고 있다.
AOP에서는 프록시 객체를 통해 외부에서 호출된 메서드를 가로채고, PointCut 매칭 여부를 판단한 뒤 해당 메서드에 매핑된 Advice를 실행한다.
스프링에서는 일관된 AOP 적용을 위해 public을 제외한 접근 제한자는 트랜잭션이 걸리지 않게 처리한다. 즉, 프록시 설정에 따라 트랜잭션 적용 여부가 결정되는 변칙적인 결과를 막기 위해 public 이외의 메서드는 AOP가 작동하지 않는다.
스프링 AOP에서 프록시는 크게 JDK Dynamic proxy 또는 CGLIB으로 작동한다. 그리고 spring boot 1.4 버전 이후 부터는 default로 CGLIB을 사용한다. CGLIB은 동적으로 상속을 통해 프록시를 생성한다. 따라서 private 메소드는 상속이 불가능하기 때문에 프록시로 만들어지지 않는다.
마찬가지로 protected 또한 정상작동하지 않는다. JDK Dynamic proxy는 인터페이스를 기반으로 동작하기 때문이다.
Spring AOP의 프록시 동작 과정을 보면 프록시를 통해 들어오는 외부 메서드 호출을 인터셉트하여 작동한다. 이러한 성격으로 인해 self-invocation(자기 자신 호출) 관련 문제가 발생하게 된다.
@Service
@Slf4j
public class RunService{
public void go(){
log.info("go!");
run();
}
public void run(){
log.info("run!);
}
}
@Aspect
@Slf4j
@Component
public class AspectService {
@Pointcut("execution(* aop.test.service..*.*.(..))")
public void before Execute(){}
@Before("beforeExecute()")
public void requestLogging(JoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
log.info(method.getName() + "() 메서드 실행 중");
}
}
RunService의 go 메서드는 내부에서 run을 호출한다. 해당 메서드 동작을 로깅하기 위한 AOP 소스 코드를 작성하여 go() 로직 실행을 테스트하면 다음과 같은 결과가 나온다.
예상 결과 로그:
go() 메서드 실행 중
go!
run() 메서드 실행 중
run!
실제 결과:
go() 메서드 실행 중
go!
run!
원인: Proxy 객체를 참조하지 않고 내부 target을 직접 참조하여 발생.
해결방안: AopContext를 통해 run 메서드 호출을 Proxy 객체를 통해 호출하도록 변경
@Service
@Slf4j
public class RunService{
public void go(){
log.info("go!");
((RunService) AopContext.currentProxy()).run();
}
public void run(){
log.info("run!);
}
}