[Spring] AOP 다시 알아보기

gyeol·2025년 10월 1일

스프링

목록 보기
47/50
post-thumbnail

AOP (Aspect Oriented Programming, 관점 지향 프로그래밍)

  • 여러 클래스(객체)에 흩어져 있는 공통 관심사(Cross-Cutting Concern) 를 모듈화해서 코드 중복을 줄이고 유지보수를 쉽게 하는 프로그래밍
  • ex) 로깅, 트랜잭션 관리, 보안 체크, 실행 시간 측정 등
  • 각 언어마다 AOP 지원 툴이 존재

언제 사용?

예를 들어, 시간을 측정하는 로직이 추가되어야 한다는 지시사항을 받았을 때..

public class MemberService {

  /**
  * 회원가입
  */
  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");
    }
  }
  
  /**
  * 전체 회원 조회
  */
  public List<Member> findMembers() {
  
    long start = System.currentTimeMillis();
    
    try {
      return memberRepository.findAll();
    
    } finally {
      long finish = System.currentTimeMillis();
      long timeMs = finish - start;
      System.out.println("findMembers " + timeMs + "ms");
    }
  }
}

→ 이렇게 각 로직마다 finish ,timeMs 사용해 시간 계산

→ 이는 코드 중복과 유지보수 어려워짐 !!!

→ 그래서 AOP를 적용하는 것

💡 그렇다면 그냥 시간 측정 로직을 따로 클래스에 만들어서 작성하는 것와 AOP를 작성하는 것은 어떤 차이가 있나?

→ 관심사의 분리(Separation of Concerns)과 유지보수에 큰 차이 존재

공통점

  • 둘 다 실행 시간을 측정할 수 있음.
  • 서비스 코드와 별도 클래스를 만들어 중복을 줄이려는 시도라는 점에서 같음.

차이점

  • 직접 만든 유틸 클래스
    • 각 메서드 안에서
      // 시간 측정을 위한 유틸 클래스
      public class TimeLogger {
          public static void measure(Runnable task) {
              long start = System.currentTimeMillis();
              try {
                  task.run();
              } finally {
                  long end = System.currentTimeMillis();
                  System.out.println("실행 시간: " + (end - start) + "ms");
              }
          }
      }
      
      // 서비스 클래스
      @Service
      public class MemberService {
      
          public void joinMember() {
              // 비즈니스 로직 전에 직접 TimeLogger 호출
              TimeLogger.measure(() -> {
                  // 실제 로직
                  System.out.println("회원 가입 처리 로직 실행");
              });
          }
      }
      혹은 TimeLogger.measure(() -> logic()); 같은 식으로 직접 호출해야 함.
    • 개발자가 일일이 삽입
  • AOP
    // 시간 측정을 담당하는 AOP 클래스
    @Aspect
    @Component
    public class TimeTraceAop {
    		// 적용할 대상 지정 -> 패키지 명으로 지정 가능
        @Around("execution(* com.example..*(..))") 
        public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            try {
                return joinPoint.proceed(); // 실제 타깃 메서드 실행
            } finally {
                long end = System.currentTimeMillis();
                System.out.println(joinPoint.getSignature() + " 실행 시간: " + (end - start) + "ms");
            }
        }
    }
    
    // com.example 패키지 안에 들어있다면 프록시가 자동으로 호출 가로채서 시간 측정
    // 서비스 클래스
    @Service
    public class MemberService {
    
        public void joinMember() {
            // 비즈니스 로직만 남음
            System.out.println("회원 가입 처리 로직 실행");
        }
    }
    • 프록시가 자동으로 모든 호출을 가로채 실행 시간 측정을 적용
    • 개발자가 서비스 로직에는 전혀 손대지 않아도 됨
구분유틸 클래스(시간 측정기)AOP
적용 방식코드 안에 직접 호출프록시가 자동 적용
코드 중복호출부마다 필요없음 (핵심 로직만 유지)
유지보수호출부 전체 수정 필요어드바이스만 수정하면 전역 반영
확장성시간 측정만트랜잭션, 보안, 로깅 등 다양하게 확장
제어 범위호출한 부분만Pointcut으로 패턴 지정 가능

주요 개념

  • Advice
    • 언제, 어떤 기능을 삽입할지 정의.
    • 종류:
      • @Before: 메서드 실행 전
      • @AfterReturning: 메서드 정상 종료 후
      • @AfterThrowing: 예외 발생 시
      • @After: 무조건 실행
      • @Around: 메서드 실행 전/후를 모두 제어
  • JoinPoint
    • 어드바이스가 적용될 수 있는 “위치”.
    • 자바에서는 메서드 실행, 객체 생성, 필드 접근 등이 가능.
    • Spring AOP는 프록시 기반 → “메서드 실행 지점”에만 적용 가능.
  • Pointcut
    • Advice를 적용할 JoinPoint를 필터링하는 조건.
    • 예: 특정 패키지, 특정 메서드 이름 패턴, 특정 어노테이션이 붙은 메서드 등.
  • Target
    • 실제 부가기능이 적용되는 비즈니스 로직 객체.
  • Aspect
    • Advice + Pointcut을 합친 부가기능 모듈.

구현 방식

  • 컴파일 시점 (예: AspectJ compiler)
  • 클래스 로딩 시점 (바이트코드 조작, LTW)
  • 런타임 시점 (프록시 기반 → 스프링 AOP 방식)

Spring AOP는 런타임 프록시 기반이므로 실행 중에 프록시 객체를 생성

프록시란?
자신이 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해 클라이언트의 요청을 받아주는 것 (대리인, 대리자)

  • 클라이언트가 타킷에 접근하는 방법 제어 위해 (프록시 패턴)
  • 타깃에 부가적인 기능 부여를 위해 (데코레이터 패턴)

Spring AOP

  • 프록시 기반 AOP
    • Spring은 원본 객체를 프록시 객체로 감싸서 부가기능을 실행.
    • JDK Dynamic Proxy (인터페이스 기반), CGLIB (클래스 기반) 사용.
  • 프록시 클래스 이름을 찍어보면:
    com.example.MyService$$EnhancerBySpringCGLIB...
    → 내부적으로 원본 객체를 들고 있으며, 호출 시 부가기능을 삽입.

OOP vs. AOP

  • OOP: 객체 단위로 기능과 책임을 나눔.
  • AOP: 여러 객체에 걸쳐 흩어지는 횡단 관심사(Cross-Cutting Concern)를 모듈화.
  • 관계: AOP는 OOP의 단점을 보완 → “관심사의 분리”를 더 철저히 실현.

횡단 관심사?

핵심 비즈니스 로직과는 연관은 없지만 여러 모듈이나 계층에 공통적으로 사용되는 기능

  • 핵심 관심사
    • 상품 주문
    • 결제 처리
    • 배송 관리
  • 횡단 관심사
    - 로깅(logging) → 누가 언제 어떤 메서드를 호출했는가
    - 보안(security) → 로그인 사용자만 접근 가능
    - 트랜잭션(transaction) → DB 작업이 중간에 끊기면 롤백
    - 성능 모니터링(performance monitoring) → 실행 시간 측정
    - 예외 처리(exception handling)
    → 이런 횡단 관심사를 Apsect로 분리해 AOP 프록시가 대신 처리하도록 함

동작 방식

  1. 컨트롤러 호출
    • 컨트롤러는 주입받은 프록시 객체의 메서드를 호출
    • (프록시는 실제 타깃 객체를 감싸고 있음)
  2. 포인트컷 검사
    • 프록시는 호출된 메서드가 Pointcut 조건에 해당하는지 확인
    • 해당되면 어드바이스 체인을 실행, 아니면 바로 타깃 메서드 호출
  3. @Around 어드바이스 진행
    • joinPoint.proceed() 호출 시:
      • 다음 어드바이스로 이동하거나, 없으면 최종적으로 원본 타깃 메서드 실행
    • proceed()를 호출하지 않으면 타깃 메서드는 실행되지 않음
  4. 타깃 메서드 실행 완료 후
    • 체인을 빠져나오면서 다음 어드바이스 실행:
      • @AfterReturning → 정상 종료 시
      • @AfterThrowing → 예외 발생 시
      • @After → 예외 여부와 관계없이 항상 실행
  5. 최종 결과 반환
    • 프록시가 결과를 받아 컨트롤러로 반환
profile
공부 기록 공간 '◡'

0개의 댓글