Spring

Spring AOP란?

윤승 2025. 5. 15. 20:45

 

Spring 프레임워크를 공부하며, AOP(Aspect-Oriented Programming)가 뭐지? 
이게 왜 필요한지? 필터랑 다른 게 뭔지?라는 궁금점이 생겼다. 
오늘은 AOP에 대해 개념이랑 적용 방법에 대해 공유해 보려 한다.

 

AOP란?
AOP(관점 지향 프로그래밍)는 공통 관심 사항(부가 기능)을 핵심 로직과 분리해서 한 곳에서 관리하도록 해주는  프로그래밍 기법이다.

 

 

 

📌 왜 AOP가 필요한가?

프로그램을 개발하다 보면 반복적으로 사용되는 코드나 가독성이 떨어지는 로직들을 자주 마주하게 되는 것을 볼 수 있다.
AOP는 이러한 반복되는 코드들을 비즈니스 로직과 분리함으로써 중복을 줄이고, 코드의 유지보수성을 향상시키는 데 큰 도움을 준다.

 

 

예시)

public class UserService {
    public void save(User user) {
        log.info("사용자 저장 시도");  // 공통 관심사
        // 사용자 저장 (핵심 관심사)
    }

    public void delete(User user) {
        log.info("사용자 삭제 시도");  // 공통 관심사
        // 사용자 삭제 (핵심 관심사)
    }
}

예시를 보면 모든 메서드마다 log.info() 같은 부가기능이 계속 반복되는걸 볼 수 있다.

이런 부가기능 들을 변경하면 모든 클래스에 수정이 필요하므로 변경도 어렵다.

 

하지만, 공통 기능을 Aspect로 모아두고, 핵심 기능은 그대로 두면 유지보수가 훨씬 쉬워진다!

 

 

📌 AOP에서 사용하는 어노테이션 정리

어노테이션 설명
@Aspect 이 클래스가 AOP를 정의하는 "관점(Aspect)" 클래스임을 나타낸다
@Component Spring Bean으로 등록되기 위한 어노테이션이다. AOP 클래스는 반드시 스프링 컨테이너에서 관리되어야 하므로 필요합니다.
@Pointcut AOP를 적용할 지점(JoinPoint) 을 정의한다. 예: 특정 패키지의 메서드, 어노테이션이 붙은 메서드 등
@Before 대상 메서드 실행 이전에 실행할 부가 기능(Advice)을 정의한다
@After 대상 메서드 실행 후에(정상 또는 예외 상관 없이) 실행된다
@AfterReturning 대상 메서드가 정상적으로 리턴된 후 실행된다.
@AfterThrowing 대상 메서드에서 예외가 발생했을 때 실행된다
@Around 메서드 실행 전후를 모두 감싸서 동작한다.

 

📌  왜 AOP에서는 log.info를 써야 할까?

 

- AOP를 활용해 로그를 출력할 때, 단순히 System.out.println()을 사용하는 것보다 log.info()와 같은 로깅 프레임워크(SLF4 J, Logback 등)를 사용하는 것이 좋다.

System.out.println()은 화면에 단순히 텍스트를 출력하는 명령으로, 로그 수준을 설정할 수 없지만, log.info(), log.debug(), log.error()와 같은 로깅은 로그 레벨을 구분해서 필요한 정보만 필터링하거나 출력할 수 있고, 로그를 파일에 남기거나 날짜별로 분리 저장을 할 수 있다.

 

콘솔 로그)

 

📌  Spring AOP의 동작 방식

  1. 스프링 컨테이너가 관리하는 Bean만 적용 대상
  2. 메서드 실행 시점에만 AOP 적용 가능 (JoinPoint는 오직 메서드)
  3. 내부적으로는 프록시 객체를 만들어 동작

 

📌  사용 예시

 

@Pointcut

@Pointcut("execution(* com.example.service..*(..))")
public void serviceMethods() {}

com.example.service 패키지 안의 모든 메서드를 AOP 대상으로 지정

 

@Before

@Before("serviceMethods()")
public void beforeAdvice(JoinPoint joinPoint) {
    log.info("Before Method: {}", joinPoint.getSignature().getName());
}
메서드 실행 전에 로그를 출력

 

@AfterReturning

@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
    log.info("Method returned: {}", result);
}

메서드가 정상적으로 리턴되었을 때 결과값을 로그에 출력

 

 

@AfterThrowing

@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
    log.error("Exception in method: {}, error: {}", joinPoint.getSignature().getName(), ex.getMessage());
}
 

예외 발생 시 로그를 출력

 

 

@Around

@Around("serviceMethods()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
    log.info("Before method: {}", pjp.getSignature().getName());
    Object result = pjp.proceed(); // 실제 메서드 실행
    log.info("After method: {}", pjp.getSignature().getName());
    return result;
}

메서드 전후에 커스텀 로직을 삽입할 수 있으며, 실행 흐름을 제어할 수 있는 강력한 기능이다.

 

 

 

AOP는 코드 곳곳에 반복되는 공통 로직을 깔끔하게 분리해서, 핵심 로직에만 집중할 수 있게 도와주는 개발자의 비서 같은 존재라고 생각하면 된다!!