Spring AOP

Hyunsoo Kim·2025년 8월 13일
0

스프링부트

목록 보기
3/4

💡 Spring AOP란?

AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 한다. 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점을 나누어 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다.
(* 모듈화: 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것)

  • 핵심적인 관점: 비즈니스 로직
  • 부가적인 관점: 데이터베이스 연결, 로깅, 파일 입출력 등

소스 코드 상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는데, 이것을 흩어진 관심사(Crosscutting Concerns)라고 부른다.

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

💡 AOP 적용 방식

프록시 객체는 내부에 실제 대상 객체(target)를 참조하고 있다.
AOP에서는 프록시 객체를 통해 외부에서 호출된 메서드를 가로채고, PointCut 매칭 여부를 판단한 뒤 해당 메서드에 매핑된 Advice를 실행한다.

💡AOP 주요 개념

  • Aspect: 흩어진 관심사를 모듈화한 것. 주로 부가기능을 모듈화함
  • Target: Aspect를 적용하는 곳(클래스, 메서드 등)
  • Advice: 실질적인 부가 기능을 담은 구현체
  • JointPoint: Advice가 적용될 위치(메서드 진입 시점, 생성자 호출 시점, 필드에서 값을 꺼낼 때 등 다양한 시점에 적용 가능)
  • PointCut: JointPoint의 상세한 스펙을 정의한 것. 구체적으로 Advice가 실행될 지점을 정할 수 있음

💡스프링 AOP 특징

  • 프록시 패턴 기반의 AOP 구현체: 접근 제어 및 부가기능을 추가하기 위함
  • 스프링 Bean에만 AOP를 적용할 수 있음
  • 모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제(중복 코드, 프록시 클래스 작성 번거로움, 객체 간 복잡도 증가)에 대한 해결책을 지원하는 것이 목표

💡스프링 AOP 단점 및 주의사항

1. public 이외의 메서드는 AOP가 걸리지 않는다.

스프링에서는 일관된 AOP 적용을 위해 public을 제외한 접근 제한자는 트랜잭션이 걸리지 않게 처리한다. 즉, 프록시 설정에 따라 트랜잭션 적용 여부가 결정되는 변칙적인 결과를 막기 위해 public 이외의 메서드는 AOP가 작동하지 않는다.
스프링 AOP에서 프록시는 크게 JDK Dynamic proxy 또는 CGLIB으로 작동한다. 그리고 spring boot 1.4 버전 이후 부터는 default로 CGLIB을 사용한다. CGLIB은 동적으로 상속을 통해 프록시를 생성한다. 따라서 private 메소드는 상속이 불가능하기 때문에 프록시로 만들어지지 않는다.
마찬가지로 protected 또한 정상작동하지 않는다. JDK Dynamic proxy는 인터페이스를 기반으로 동작하기 때문이다.

2. 같은 클래스 내에서 트랜잭션이 걸린 메소드를 호출하면 트랜잭션이 작동하지 않는다.

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!);
  }         
}
profile
다부진 미래를 만들어가는 개발자

0개의 댓글