AOP와 SpringAOP

띠용·2025년 6월 16일

우테코 7기 BE

목록 보기
7/12

1. AOP

public void someMethod() {
	...
	mainBusiness();
	...
}

나는 위 메서드에서 mainBusiness()를 수행하는데 걸리는 시간을 체크해 로그로 남기고 싶다.

public void someMethod() {
	stopWatch.start();
	try{
		...
		mainBusiness();    // 핵심 비즈니스 로직 호출
		...
	} finally {
		stopWatch.stop();
		log.info("printing spent {} ms", stopWatch.getLastTaskTime());
	}
}

그래서 위와 같이 작업했다. 그런데 100개의 서로 다른 메서드에 대해 동일하게 수행 시간 로깅을 남겨야 한다면 얼마나 귀찮을까?

위 상황에서 mainBusiness() 는 핵심 비즈니스 로직에 해당하고, 부가 기능인 로깅을 위해 추가된 나머지 코드는 인프라 로직에 해당한다.

인프라 로직은 특정 메서드가 아니라 애플리케이션의 전 영역에서 필요할 수 있고, 그때마다 중복 코드가 생긴다. 또한 비즈니스 로직 주변이 지저분해져 비즈니스 로직을 한번에 파악하기 어렵게 만든다.

이렇게 애플리케이션 영역에서 횡단으로 중복되는 인프라 로직을 ‘횡단 관심사’라고 한다.

AOP(Aspect Oriented Programming, 관점 지향 프로그래밍) 은 핵심 비즈니스 로직으로부터 ‘횡단 관심사’를 분리하는 것에 목적을 둔다.

즉, 핵심 기능과 부가 기능(Aspect)을 분리하여 부가기능을 별도의 모듈로 관리하자는 것이 AOP 이다.

이를 통해 코드 중복을 줄이고, 가독성을 향상 시킬 수 있다. (유지 보수 용이)


AOP 용어

AOP의 작동 방식을 조금 더 자세히 이해하기 위해 AOP 에서 사용되는 용어를 알아보자.

  • Aspect : 횡단 관심사를 모듈화 한 것. 주로 부가기능을 모듈화 함(ex. 로그 찍기…)
    • 여러 pointcut과 advice를 포괄하는 개념
  • Target : 부가기능을 부여할 대상 (ex. 클래스, 메서드 ...)
  • Advice : 실질적인 부가기능의 구현체
  • Join Point : Advice가 코드 상 실제 개입하는 지점 (ex. 메서드 실행 직전, 직후 등의 맥락)
  • Pointcut : 여러 Join Point 중 실제로 Advice를 적용할 대상을 선별하는 조건

예시를 통해 자세히 알아보자. Join Point등 당장 이해되지 않는 개념은 뒤에 나올 SpringAOP에서의 예시까지 살펴보면 이해가 될 것이다.


예시 : 로그인 시 로그 남기기

@Aspect
@Component
public class LoggingAspect {
    
    @Before("execution(* com.example.service.UserService.login(..))")
    public void logBeforeLogin() {
        System.out.println("🔐 로그인 시도됨");
    }
}
  • 위 클래스 전체가 Aspect에 해당한다. (보시다시피 어노테이션도 붙어있다.)
  • @Aspect 가 붙은 위 클래스는 ‘로그 남기기’ 라는 횡단 관심사 모듈을 담당한다.
@Before("execution(* com.example.service.UserService.login(..))")
public void logBeforeLogin() {
    System.out.println("🔐 로그인 시도됨");
}
  • 그 중 logBeforeLogin() 와 같이 @Before, @After, @Around 등이 붙은 메서드가 advice 이다.
  • 실제로 언제, 무엇을 할 지 정의하는 실행 코드이다.

2. 스프링 AOP

스프링 AOP(Spring AOP)는 스프링 컨테이너에 의해 관리되는 빈에 AOP를 적용할 수 있게 해 주는 지원 모듈이다.

프록시(Proxy)

스프링 AOP는 프록시 패턴을 활용해 AOP를 구현했다.

프록시 패턴은 실제 객체 대신 그 객체를 대리하는 객체를 사용하도록 하는 패턴이다. 이해를 위해 예를 들어보자.

실제로 AOP를 적용하지 않은 클래스를 주입받아 이름을 찍어보면 정의한 그대로 찍힌다.

public AuthController(AuthService authService) {
	System.out.println(authService.getClass().getName());
	this.authService = authService;
}

org.woowacouse.aoppractice.service.AuthService

하지만 AuthService에 AOP를 적용하고 같은 동작을 수행하면 아래와 같은 프록시 객체를 확인 할 수 있다.

org.woowacouse.aoppractice.service.AuthServiceEnhancerBySpringCGLIBEnhancerBySpringCGLIB81be6d68

Spring은 이렇게 기존 객체 대신 부가 기능을 추가한 프록시 객체를 호출하는 방식으로 AOP를 구현한다.


의존성

AOP가 모든 스프링 앱에서 반드시 필요한 기능은 아니기 때문에 기본 의존성에 포함 되어있지 않다.

따라서 사용을 위해선 별도로 의존성을 추가해줘야만 한다.

implementation 'org.springframework.boot:spring-boot-starter-aop'

사용 예시

바로 예시를 통해 spring AOP를 공부해보자.

@Service
public class UserService {
    public void login(String username) {
        // 로그인 로직
        System.out.println(username + "님이 로그인했습니다.");
    }
}

위와 같은 서비스 로직에 로깅을 남기려고 한다면 아래와 같은 @Aspect를 빈으로 등록할 수 있다.

@Aspect
@Component
public class LoginLoggingAdvisor {

    // UserService의 login(String) 메서드를 포인트컷으로 지정한다는 뜻
    @Pointcut("execution(* com.example.service.UserService.login(..))")
    public void loginMethod() {}

    // 포인트컷(loginMethod())의 실행 직전(@Before)에 행할 동작을 정의한다
    @Before("loginMethod()")
    public void logBeforeLogin(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("💡 로그인 시도 메서드 호출됨: " + methodName + ", 사용자명: " + args[0]);
    }
}

각 어노테이션 별 의미와 사용법만 잘 숙지하면 어렵지 않게 Spring AOP를 적용할 수 있다.

  • @Aspect : 이 클래스가 AOP기능을 정의함을 나타냄

  • @Pointcut(…) : 타깃 메서드(포인트컷)를 선택

    • Pointcut을 나타내는 이 요상한 문법은 AOP 구현 프레임워크인 AspectJ의 문법이다.
    • 포인트컷을 원하는 위치에 설정하기 위해 AspectJ 문법을 공부해야 한다. (구글링 해라)
    • Spring AOP가 내부적으로 AspectJ를 사용하지는 않는다. 문법만 차용했다.
  • @Before : 어드바이스 실행 시점이 포인트컷 실행 직전임을 의미

    • @After , @Around 도 있다.
  • JoinPoint : 실제로 메서드가 호출된 순간에 대한 컨텍스트 정보를 담고 있는 객체
    • 예시의 UserService.login() 메서드가 호출되는 그 순간의 맥락을 담고 있는 객체이다.
    • 메서드 이름, 전달된 파라미터 등 다양한 정보를 추출할 수 있다.
      @Before("loginMethod()")
      public void logBeforeLogin(JoinPoint joinPoint) {
          System.out.println("🎯 메서드 이름: " + joinPoint.getSignature().getName());
          System.out.println("📦 전달된 파라미터: " + Arrays.toString(joinPoint.getArgs()));
          System.out.println("📍 호출 대상 객체: " + joinPoint.getTarget());
      }
      
      메서드설명
      getSignature()호출된 메서드의 이름/정보
      getArgs()전달된 파라미터 배열
      getTarget()실제 타깃 객체(UserService 인스턴스)
      getThis()프록시 객체 (보통 Spring이 만든 프록시)

참고

콩하나의 스프링 AOP
제이의 Spring AOP
[Spring] 스프링 AOP (Spring AOP) 총정리

0개의 댓글