어제 오늘 내일

[Spring Boot] "복사-붙여넣기 멈춰!" AOP로 중복 코드 싹둑 자르기 (feat. 실행 시간 측정) 본문

IT/SpringBoot

[Spring Boot] "복사-붙여넣기 멈춰!" AOP로 중복 코드 싹둑 자르기 (feat. 실행 시간 측정)

hi.anna 2026. 3. 16. 10:39

 
여러분, 혹시 이런 코드를 짜보신 적 있나요?

public void join(Member member) {
    long start = System.currentTimeMillis(); // 1. 시작 시간 측정

    try {
        memberRepository.save(member); // 핵심 로직 (이거 하나만 진짜임!)
    } finally {
        long finish = System.currentTimeMillis(); // 2. 종료 시간 측정
        long timeMs = finish - start;
        System.out.println("join = " + timeMs + "ms"); // 3. 로그 출력
    }
}

회원 가입, 조회, 수정... 모든 메서드마다 저 시간 측정 코드(1, 2, 3번)를 넣어야 한다면? 끔찍합니다. 핵심 로직이 눈에 들어오지도 않죠.
이때 필요한 것이 바로 AOP(Aspect Oriented Programming)입니다.


1. AOP가 뭔가요? (핵심 관심사 vs 공통 관심사)

AOP는 프로그램을 두 가지 관점으로 바라봅니다.

  1. 핵심 관심사 (Core Concern): 우리가 진짜 하고 싶은 일 (회원 가입, 주문, 결제 등)
  2. 공통 관심사 (Cross-cutting Concern): 여러 곳에서 공통적으로 쓰이는 일 (로그, 보안, 트랜잭션 등)

AOP는 이 공통 관심사를 따로 떼어내서 관리하고, 실행할 때 원하는 곳에 자동으로 적용해 주는 기술입니다. 마치 샌드위치의 빵(공통)과 속재료(핵심)를 따로 준비하는 것과 같습니다.


2. AOP 용어 정리 (면접 필수!)

AOP를 쓰려면 용어 4가지만 알면 됩니다. (조금 어렵지만 중요해요!)

  1. Aspect (관점): 공통 관심사 그 자체. (예: LogAspect 클래스)
  2. Advice (조언): "언제, 무엇을 할 거야?" (예: 메서드 실행 에 로그를 남겨라)
  3. Pointcut (포인트컷): "어디에 적용할 거야?" (예: com.example.service 패키지 하위의 모든 메서드)
  4. JoinPoint (조인포인트): 적용 가능한 지점. (메서드 실행 시점, 생성자 호출 시점 등)

3. 실전 예제: 어노테이션 하나로 실행 시간 측정하기

가장 많이 쓰는 예제입니다. @LogExecutionTime이라는 어노테이션을 붙이면 자동으로 실행 시간을 찍어주는 AOP를 만들어보겠습니다.

① 커스텀 어노테이션 만들기

@Target(ElementType.METHOD) // 메서드에 붙일 거야
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 유지할 거야
public @interface LogExecutionTime {
}

② Aspect 구현하기 (여기가 핵심!)

@Slf4j
@Aspect // 1. AOP 클래스다!
@Component // 빈으로 등록해야 함
public class LogTraceAspect {

    // 2. Pointcut: @LogExecutionTime 어노테이션이 붙은 곳에 적용해라
    @Around("@annotation(LogExecutionTime)")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {

        long start = System.currentTimeMillis();

        log.info("START: {}", joinPoint.toString()); // 메서드 시작 로그

        try {
            // 3. ★ 핵심 로직 실행! (join.proceed()가 실제 메서드를 호출함)
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;

            log.info("END: {} {}ms", joinPoint.toString(), timeMs); // 종료 및 시간 로그
        }
    }
}

③ 사용하기

이제 비즈니스 로직은 아주 깨끗해집니다. 어노테이션 하나만 붙이면 되니까요!

@Service
public class MemberService {

    @LogExecutionTime // 이제 이 메서드는 실행 시간이 자동 측정됩니다.
    public Long join(Member member) {
        memberRepository.save(member);
        return member.getId();
    }
}

4. AOP 동작 원리: 프록시(Proxy)

"도대체 코드를 안 고쳤는데 어떻게 중간에 끼어드는 거죠?"
스프링 AOP는 프록시(Proxy) 패턴을 사용합니다.

  1. 스프링이 실행될 때, MemberService 빈(Bean)을 만듭니다.
  2. 이때 AOP가 적용된 클래스라면, 스프링은 가짜(Proxy) MemberService를 만들어서 갈아끼웁니다.
  3. 클라이언트가 join()을 호출하면 -> 가짜가 먼저 받아서 시간을 측정하고 -> 진짜 join()을 호출합니다.

5. Interceptor vs AOP: 뭐가 달라요?

지난 시간에 배운 인터셉터와 헷갈리시죠? 결정적인 차이가 있습니다.

구분 Interceptor (인터셉터) AOP(관점 지향)
적용 대상 URL (웹 요청) 메서드 (Service, Repository 등)
파라미터 Request, Response 객체 메서드의 매개변수 (Object[] args)
주 사용처 로그인 체크, 권한 인증 트랜잭션, 로깅, 실행 시간 측정
특징 컨트롤러 앞뒤만 가능 어디든 원하는 메서드에 적용 가능

결론: 웹과 관련된 공통 처리는 Interceptor, 비즈니스 로직의 공통 처리는 AOP를 쓰세요.


마치며

오늘의 결론입니다.

  1. AOP는 핵심 로직에서 공통 로직(로그, 트랜잭션 등)을 분리하는 기술이다.
  2. @Aspect, @Around, ProceedingJoinPoint 세 가지만 기억하면 구현할 수 있다.
  3. 스프링 AOP는 프록시(Proxy)를 통해 동작한다.

이제 여러분의 코드는 군더더기 없이 비즈니스 로직에만 집중할 수 있게 되었습니다.
다음 포스팅에서는 개발이 다 끝났으니 배포를 해야겠죠? "Jar 파일 하나면 끝!" Gradle 빌드 도구와 실행 가능한 Jar(Executable Jar) 만들기에 대해 알아보겠습니다.
 

반응형
Comments