AOP와 Spring AOP 로깅 전략

EunBeen Noh·2025년 5월 15일

SpringAdvanced

목록 보기
14/16

개발하다보면, 핵심 로직 외에도
로그, 트랜잭션, 예외 처리 같은 반복되는 부가 작업이 자주 등장하게 된다.

이런 코드가 여기저기 흩어지면 중복이 생기고 유지보수가 어려워지는데,
이를 해결해주는 개념이 바로 AOP(관점 지향 프로그래밍)이다.

0. AOP

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

AOP는 프로그램을 다음과 같은 관점으로 나누어 바라본다.

구분설명
핵심 관심사애플리케이션의 핵심 비즈니스 로직
부가 관심사로그, 트랜잭션, 예외 처리처럼 반복적으로 들어가는 기능

AOP에서는 로그 출력, 트랜잭션 처리 같은 공통된 기능(=부가 관심사) 을
핵심 로직에서 따로 떼어내서 별도의 재사용 가능한 단위(=Aspect) 로 모듈화한다.

  • 모듈화: 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것

이와 같이, AOP는 공통된 관심사를 가진 코드를 모듈화함으로써,
중복을 줄이고 코드의 가독성과 유지보수성을 향상시킨다.

여기서 소스코드에서 각 부분에 계속 반복해서 쓰는 코드들을
흩어진 관심사(Crosscutting Concerns)라고 한다. (이후에는 횡단 관심사라고 칭한다.)

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

횡단 관심사(Crosscutting Concerns)

  • 여러 모듈이나 클래스에서 반복해서 등장하지만, 본질적이지 않은 기능
    • 모든 서비스에 필요한 로그 출력
    • 모든 데이터 처리에 필요한 트랜잭션 관리
    • 모든 API에 필요한 예외 로깅
  • 이러한 기능은 핵심 로직과는 별개지만, 코드 곳곳에 흩어져 있기 때문에
    관심사가 뒤섞이고, 책임이 불분명해지는 문제가 발생한다.
    → AOP는 이 횡단 관심사를 Aspect라는 독립 모듈로 분리하여 해결한다.

AOP 관련 개념

용어의미
Aspect부가 관심사를 모듈화한 단위
Advice언제, 무엇을 할 것인지 정의한 코드 (부가기능 구현체)
Join PointAdvice가 적용될 수 있는 시점 (ex. 메서드 호출 전/후)
PointcutAdvice가 적용될 Join Point를 선별하는 조건
TargetAspect가 적용되는 실제 대상 객체

1. 스프링 AOP

스프링 AOP는 프록시(Proxy) 패턴을 기반으로 동작한다.

즉, 실제 객체(Target)를 감싸는 프록시 객체를 생성하고,
이 프록시 객체가 핵심 로직 전후로 부가 로직을 실행하는 방식이다.

  • 프록시: 진짜 객체를 대신해서 동작하는 대리 객체

스프링 AOP의 특징

  • 스프링 컨테이너에 등록된 빈(Bean) 에 대해서만 AOP 적용 가능
  • 인터페이스 기반의 JDK 동적 프록시 또는 CGLIB 프록시를 사용
  • 일반적으로 @Aspect 어노테이션을 이용해 AOP 구현

CGLIB(Code Generator Library)

코드 생성 라이브러리로서 런타임에 동적으로 자바 클래스의 프록시를 생성해주는 기능을 제공한다.
인터페이스가 아닌 클래스에 대해서 동적 프록시를 생성할 수 있다.

공통 기능을 @Aspect 로 정의하면,
스프링이 자동으로 프록시 객체를 만들어
실제 로직 앞뒤에 공통 기능을 자동으로 끼워넣어 실행해 준다.

2. AOP 로깅(logging) 전략

모든 메서드에서 로그를 일일이 작성하는 대신,
공통된 로깅 로직을 Aspect로 분리하면 훨씬 깔끔하고 유지보수도 쉬워진다.

각 메서드에서 직접 로그를 작성하는 경우

  • 간단하지만, 모든 서비스마다 비슷한 로그 코드를 계속 작성해야 하므로
    중복이 많고, 형식이 일관되지 못할 수 있으며, 실수로 누락될 가능성도 있다. (+ 귀찮다)
@Slf4j
@Service
public class MemberService {

    public void join(String name) {
        // 회원 가입 처리
        log.info("회원 가입 - name={}", name);  // → AOP로 대체 가능
    }

    public String find(String name) {
        // 회원 조회 처리
        log.info("회원 조회 - name={}", name);  // → AOP로 대체 가능
        return "Member(" + name + ")";
    }
}

AOP 적용 후

다음과 같이 AOP를 적용하게 되면,
공통된 로깅 규칙을 한 곳에 정의할 수 있어 범위 내 모든 메서드에 자동으로 적용할 수 있다.
또한, 로깅, 예외 처리 등 부가적인 작업은 AOP가 대신 처리해주기 때문에 유지보수가 쉽다.
→ 중복 코드, 포맷 일관성, 실수, 귀찮음을 모두 해결할 수 있는 것이다.

또한, MemberService 코드에서는 비즈니스 로직만 작성하면 되므로
핵심 로직에만 집중할 수 있고, 코드의 역할과 책임이 명확해진다.

@Aspect
@Component
@Slf4j
public class LogAspect {

    @Before("execution(* com.example.member..*(..))")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().toShortString();
        log.info("[시작] Member 메서드 실행: {}", methodName);
    }

    @AfterReturning(pointcut = "execution(* com.example.member..*(..))", returning = "result")
    public void logAfterReturn(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().toShortString();
        log.info("[성공] {} → 반환값: {}", methodName, result);
    }

    @AfterThrowing(pointcut = "execution(* com.example.member..*(..))", throwing = "ex")
    public void logException(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().toShortString();
        log.error("[예외] {} → 메시지: {}", methodName, ex.getMessage(), ex);
    }
}

Advice 종류

Advice 종류설명
@Before메서드 실행 전에 로그 출력
@AfterReturning메서드가 성공적으로 끝났을 때 로그 출력
@AfterThrowing메서드 실행 중 예외 발생 시 로그 출력

0개의 댓글