스프링 AOP

__〆( ̄ー ̄ ) ·2025년 2월 7일

스프링 부트

목록 보기
6/9

AOP(Aspect-Oriented Programming)란?

AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)은 프로그램의 핵심 로직과 부가 기능을 분리하여 코드의 모듈화와 재사용성 향상을 목적으로하는 프로그래밍 방법론이에요.

AOP의 주요 개념은 다음과 같아요.

  • 횡단 관심사(Cross-cutting Concerns): 여러 기능들에서 공통적으로 사용되는 부가 기능
  • 관점(Aspect): 횡단 관심사를 모듈화하여 재사용할 수 있도록 한 것
  • 조인 포인트(Join Point): Aspect가 삽입될 수 있는 지점 (메소드 호출, 예외 발생 등)
  • 포인트컷(Pointcut): 조인 포인트 정의
  • 어드바이스(Advice): Aspect에서 수행되는 부가 기능
  • 위빙 (Weaving): 조인 포인트에 어드바이스를 삽입하는 과정

가장 대표적으로 AOP를 적용하는 경우는 로깅이에요. 아래는 간단한 Logging Aspect 예시입니다.

@Aspect
@Component
public class LoggingAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint joinPoint) {
        logger.info("메서드 실행 전: {}", joinPoint.getSignature());
    }
}

위 코드에서 각 개념에 해당하는 코드들은 다음과 같아요

  • logger.info("메서드 실행 전: {}", joinPoint.getSignature());: 횡단 관심사에 해당하는 부분이며 public void before 메소드를 어드바이스라고 볼 수 있어요.
  • @Aspect: 해당 클래스가 Aspect임을 나타내요.
  • @Before: 해당 어드바이스가 메소드 실행 전에 실행됨을 의미해요.
  • execution(* com.example.service.*.*(..)): 포인트 컷에 해당돼요. com.example.service 하위 패키지 모든 클래스 및 메소드에 어드바이스를 적용해요.
  • JoinPoint: 조인 포인트에 해당 돼요. 해당 코드에서는 호출되는 메소드를 의미해요.

위빙은 소스 코드에서는 알 수 없어요. AOP를 지원하는 두 가지 라이브러리의 방식을 보면 Spring AOP는 프록시 기반 방식을 AspectJ는 바이트 코드를 조작하는 방식을 사용해요.

위빙

Spring AOPAspectJ는 위빙 방식과 시점이 서로 달라요.

Spring AOP 위빙

Spring AOP는 프록시 기반으로 AOP를 구현해요. 실제 객체를 접근하기 위한 프록시 을 생성하고 프록시를 통해 AOP를 적용해요. 그래서 Spring AOP 객체에만 AOP를 적용할 수 있어요.

런 타임에 프록시를 기반으로 하고 있어서 메모리나 성능 측면에서 단점이 있을 수는 있지만 지금은 거의 의미가 없는 수준이에요. 스프링을 사용하고 있다면 AspectJ 보다 적용하기 쉽기 때문에 많이 사용해요.

AspectJ 위빙

AspectJ는 컴파일 시점이나 클래스 로딩 시점에 바이트 코드에 어드바이스를 적용해요. 따라서 모든 자바 객체에 어드바이스를 적용할 수 있고 런타임 오버헤드가 거의 없어요.

Spring AOP 대비 더 다양한 기능을 지원하지만 적용하기 어려워 정말 복잡한 AOP 기능이 필요한 경우에 사용돼요.

조인 포인트 (Join Point)

조인 포인트는 @Before말고 @After, @After, @AfterReturning, @AfterThrowing, @Around와 같은 것들이 있어요.

@Before

조인 포인트 전 실행되는 어드바이스를 의미해요.

@Before("execution(* com.example.MyService.doSomething(..))")
public void beforeDoSomething() {
    System.out.println("doSomething 메서드 실행 전");
}

@After

조인 포인트 후 실행되는 어드바이스를 의미해요.

@After("execution(* com.example.MyService.doSomething(..))")
public void afterDoSomething() {
    System.out.println("doSomething 메서드 실행 후");
}

@AfterReturning

조인 포인트가 정상적으로 실행된 후 실행되는 어드바이스를 의미해요.

@AfterReturning(pointcut = "execution(* com.example.MyService.doSomething(..))", returning = "result")
public void afterReturningDoSomething(Object result) {
    System.out.println("doSomething 메서드 정상 실행 후 반환 값: " + result);
}

@After는 예외가 발생해도 실행되지만 @AfterReturning은 예외를 던지면 실행되지 않아요. 또한 @After와는 다르게 반환 값(Object result)에 접근할 수 있어요.

@AfterThrowing

조인 포인트에서 예외가 발생했을 때 실행되는 어드바이스를 의미해요.

@AfterThrowing(pointcut = "execution(* com.example.MyService.doSomething(..))", throwing = "ex")
public void afterThrowingDoSomething(Exception ex) {
    System.err.println("doSomething 메서드 실행 중 예외 발생: " + ex.getMessage());
}

@Around

조인 포인트 전후에 모두 실행되는 어드바이스를 의미해요.,

@Around("execution(* com.example.MyService.doSomething(..))")
public Object aroundDoSomething(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("doSomething 메서드 실행 전");
    Object result = joinPoint.proceed(); // Join Point 실행
    System.out.println("doSomething 메서드 실행 후");
    return result;
}

joinPoint.proceed() 함수 호출을 통해 해당 메서드를 호출하는 부분이 필요해요. @Around의 경우 조인 포인트의 실행 흐름을 제어할 수 있어서 신중하게 사용해야해요.

포인트컷 (Pointcut)

조인 포인트를 정의하는 포인트컷은 다양한 표현식들을 지원하는데 , within, this, target, args, @annotation, @within, @target, @args, bean과 같은 포인트컷 지시자 (Pointcut Designator)들이 있어요.

execution

메소드 실행에 대한 조인 포인트를 매칭해요. 이 포인트컷 지시자에는 아래와 같은 패턴을 명시하여 조인 포인트를 찾아요.

  • modifiers-pattern?: 접근 제어자 (public, private 등) (생략 가능)
  • ret-type-pattern: 반환 타입
  • declaring-type-pattern?: 클래스 또는 인터페이스 타입 (생략 가능)
  • name-pattern: 메서드 이름
  • args-pattern?: 메서드 인자 (생략 가능)
  • throws-pattern?: 예외 발생 (생략 가능)

이 포인트컷 표현식의 예시를 볼게요.
execution(public String com.example.MyService.getName(int))

위 포인트컷은 com.example.MyService 클래스의 getName 메서드 (int 타입 인자를 가지고 String 타입을 반환하는 public 메서드) 실행 시점을 의미해요.

execution 지시자는 거의 대부분의 경우를 표현할 수 있어서 가장 많이 사용되는 지시자에요. 다만 포인트컷 표현식이 너무 복잡하다면 다른 지시자를 사용해서 간략화하는 것이 바람직해요.

within(type-pattern)

특정 타입 내의 조인 포인트를 의미해요. 일반적으로는 패키지 경로나 클래스명을 사용해요.

within(com.example.service.*)

this(type-pattern)와 target(type-pattern)

Spring AOP는 프록시 방식으로 AOP를 적용하기 위해 프록시 빈을 생성한다고 했어요. this 지시자는 그 프록시 빈을 조인 포인트로하고 target 지시자는 실제 객체를 조인 포인트로 해요.

표현 방식은 동일해요.
this(com.example.MyService)
target(com.example.MyService)

일반적으로 target 지시자 외에는 프록시 빈을 조인 포인트로 사용해요. 실제 빈 객체의 상태를 조회하고 싶은 경우에 target 지시자를 사용해요.

args (type-pattern)

특정 인자 타입 인스턴스에 조인 포인트를 의미해요

args(String)

@annotation (annotation-type)

특정 어노테이션이 있는 조인 포인트를 의미해요

@annotation(com.example.MyAnnotation)

@within (annotation-type)

특정 어노테이션이 있는 타입 내의 모든 조인 포인트를 의미해요. @annotaion과 달리 클래스에 있는 어노테이션 타입만 검사해요.

@within(com.example.MyAnnotation)

@target (annotation-type)

실제 빈 객체에 특정 어노테이션이 적용된 메소드를 의미해요.

@target(com.example.MyAnnotation)

@args (annotation-type)

메소드 파라미터에 특정 어노테이션이 적용된 메소드를 의미해요.

@args(com.example.MyAnnotation)

bean (bean-name)

스프링 빈의 이름을 의미해요

bean(myService)

포인트컷 표현식은 &&, ||, !과 같은 논리 연산자를 조합해서 사용할 수도 있어요. 다만 복잡한 포인트컷은 지양하는 것이 좋아요.

profile
뭐라도 적자

0개의 댓글