어제 오늘 내일

[Spring Boot] AOP 심화 분석 - LogAspect 코드 뜯어보기 & 문법 완벽 정리 본문

IT/SpringBoot

[Spring Boot] AOP 심화 분석 - LogAspect 코드 뜯어보기 & 문법 완벽 정리

hi.anna 2026. 2. 27. 07:31

지난 포스팅에서 우리는 LogAspect 클래스를 이용해 로그를 자동화했습니다.
하지만 코드가 어떻게 동작하는지 정확히 모르고 쓴다면, 나중에 "Service 계층에도 로그를 찍고 싶은데?" 라거나 "리턴 값을 바꾸고 싶은데?" 같은 상황이 왔을 때 응용하기 어렵습니다.

오늘은 LogAspect 코드를 한 줄씩 해부하고, Pointcut 작성법부터 다양한 어노테이션까지 AOP의 핵심 문법을 마스터해 보겠습니다.


1. 전체 코드 다시 보기

먼저 분석할 대상인 LogAspect.java의 전체 코드입니다.

@Slf4j
@Aspect      // (1) AOP 클래스 명시
@Component   // (2) Bean 등록
public class LogAspect {

    // (3) Pointcut: 적용 범위 설정 (서명)
    @Pointcut("execution(* com.example.board.controller..*.*(..))")
    public void controller() {}

    // (4) Advice: 실행 시점 설정 (@Around)
    @Around("controller()")
    public Object logging(ProceedingJoinPoint joinPoint) throws Throwable {

        // (5) 사전 처리 (Before)
        log.info("요청 시작!");
        long start = System.currentTimeMillis();

        try {
            // (6) 타겟 메서드 실행 (Proceed)
            Object result = joinPoint.proceed();

            // (7) 사후 처리 (After Returning)
            long end = System.currentTimeMillis();
            log.info("요청 종료! 시간: {}ms", end - start);

            return result;

        } catch (Throwable e) {
            // (8) 예외 처리 (After Throwing)
            log.error("에러 발생!");
            throw e;
        }
    }
}

2. Pointcut 상세 분석: "어디에 적용할까?"

코드의 (3)번 부분입니다. AOP에서 가장 어렵게 느껴지는 부분이죠.

@Pointcut("execution(* com.example.board.controller..*.*(..))")
public void controller() {} 

Q1. 라는 텅 빈 메서드는 왜 필요한가요?

이 메서드는 포인트컷 서명(Signature)이라고 부릅니다. 쉽게 말해 "긴 표현식에 별명을 붙이는 것"입니다.

만약 이 메서드가 없다면, 우리는 @Around, @Before 등을 쓸 때마다 저 긴 execution(...) 문자열을 계속 복사/붙여넣기 해야 합니다.

  • 별명 사용: @Around("controller()") ➡️ 깔끔! ✨
  • 직접 입력: @Around("execution(* com.example.board....)") ➡️ 지저분하고 오타 위험! 😱

Q2. Pointcut 표현식 작성법 (문법 해부)

가장 많이 쓰는 execution 지시자의 문법은 다음과 같습니다.

공식: execution(접근제어자? 리턴타입 패키지.클래스.메서드(파라미터) 예외?)

우리가 쓴 코드를 대입해 볼까요?

  • * (맨 앞): 리턴 타입. void, String, List무엇이든 상관없다는 뜻.
  • com.example.board.controller: 패키지 경로.
  • ..: 하위 패키지 포함. (controller 폴더 안에 api 폴더가 있어도 포함됨)
  • * (클래스 자리): 모든 클래스. (MemberController, BoardController 등)
  • .* (메서드 자리): 모든 메서드. (login, join 등)
  • (..): 파라미터. 개수가 0개든 10개든, 타입이 뭐든 상관없음.

💡 응용해보기 (퀴즈)

  1. Service 패키지의 모든 메서드에 적용하려면?
    • execution(* com.example.board.service..*.*(..))
  2. 메서드 이름이 'find'로 시작하는 것만 적용하려면?
    • execution(* com.example..*.find*(..))

3. Advice 상세 분석: "언제 실행할까?" (어노테이션 종류)

코드의 (4)번 부분입니다. 우리는 @Around를 썼지만, 상황에 따라 더 적절한 어노테이션들이 있습니다.

어노테이션실행 시점특징 및 용도
@Before메서드 실행 타겟 메서드를 막을 순 없습니다.
(예: 권한 체크, 데이터 유효성 검사 로그)
@AfterReturning메서드 성공 후메서드가 값을 반환한 직후 실행됩니다.
(예: 리턴 데이터 로깅, 결과값 가공)
@AfterThrowing예외 발생 시에러가 났 때만 실행됩니다.
(예: 에러 감지 알림, 트랜잭션 롤백 로그)
@After무조건 (성공/실패 무관)자바의 finally와 같습니다.
(예: 리소스 반환, 연결 종료)
@Around전 + 후 + 예외 (만능)메서드 실행을 직접 제어할 수 있는 가장 강력한 어드바이스입니다.

시간 측정이나 트랜잭션 관리는 이것만 가능합니다.

 


4. @Around 작성법과 ProceedingJoinPoint

@Around는 가장 강력한 만큼 작성법이 조금 까다롭습니다.

@Around("controller()")
public Object logging(ProceedingJoinPoint joinPoint) throws Throwable { ... }

Q1. 가 뭔가요?

AOP가 가로챈 "실제 실행 중인 메서드 정보"가 담긴 객체입니다.

  • joinPoint.getSignature().getName(): 실행된 메서드 이름 (예: login)
  • joinPoint.getArgs(): 메서드에 들어온 파라미터들 (예: userDto)

주의: ProceedingJoinPoint는 오직 @Around에서만 사용할 수 있습니다. (@Before 등에서는 JoinPoint를 씁니다.)

Q2. 는 왜 중요한가요?

이 코드는 "원래 가려던 길(Controller) 계속 가세요" 라는 명령어입니다.

  • 이 줄 위쪽에 쓴 코드는 @Before 시점에 실행됩니다.
  • 이 줄 아래쪽에 쓴 코드는 @After 시점에 실행됩니다.
  • ⚠️ 만약 이 코드를 빼먹으면? 요청이 컨트롤러로 넘어가지 않고 여기서 멈춥니다. (서버 먹통의 원인!)

Q3. 왜 리턴 타입이  인가요?

AOP는 login 메서드(리턴: JwtToken)에도 걸리고, test 메서드(리턴: String)에도 걸립니다.
어떤 타입이 올지 모르기 때문에, 자바의 최상위 타입인 Object로 받아서 그대로 토스(return result)해줘야 합니다.


5. 전체 동작 흐름 (Sequence Diagram)

이 복잡한 과정이 실제로는 어떻게 흘러가는지 그림으로 정리해 봅시다.


🎉 마무리

AOP는 처음엔 낯설지만, "Pointcut으로 위치를 잡고, Advice로 타이밍을 잡는다"는 원리만 알면, 로깅뿐만 아니라 권한 처리, 트랜잭션 등 고급 기능을 자유자재로 구현할 수 있습니다.
 
 

반응형
Comments