본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로웹 개발 › 백엔드 › Spring

AOP(Aspect Oriented Programming)

목차

AOP(Aspect Oriented Programming)

AOP는 ‘관점 지향 프로그래밍’이라 불리며, 객체 지향 프로그래밍(OOP)의 부족한 부분을 보완하기 위해 등장한 프로그래밍 패러다임이다.

AOP는 OOP와 달리 횡단적 관심사(cross-cutting concern)를 분리하여 모듈화하고, 이를 필요한 부분에 적용하는 방식으로 동작합니다.

  • 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나눈 후 나눈 관점으로 분리한 기준들을 모듈화(공통된 로직)

    • 핵심적인 관점: 개발자가 적용 하고자 하는 비지니스 로직
    • 부가적인 관점: 핵심 로직을 수행하기 위한 로깅, 보안, 트랜잭션 처리 등
  • 관심사는 ‘Aspect’라는 단위로 추상화하며, 이 Aspect를 프로그램 코드에 적용하는 방식으로 동작한다.

AOP는 주로 로깅, 보안, 트랜잭션 처리 등의 관심사를 분리하여 모듈화하고, 런타임 시에 필요한 부분에 적용하는데 많이 사용된다.

1. AOP 용어

  • Target : 부가기능을 부여할 대상으로 Aspect가 적용되는 곳이다(Class, Method 등)

  • Advice : Target에게 제공할 부가 기능을 담은 모듈로 실제 기능을 정의한 구현체이다.

  • JoinPoint : Advice가 적용될 위치/지점이다. 스프링 프록시 AOP에서 조인포인트는 메서드 실행 단계 에서만 동작한다.

  • Pointcut : Advice를 적용할 메서드 실행 지점을 선별한다.

  • Aspect : 흩어진 관심사들을 모듈화 한 것 으로 한 개 또는 그 이상의 [Pointcut + Advice]으로 만들어진다.

2. 스프링 AOP

  • 스프링은 AOP는 프록시 기반의 AOP 구현체다.

  • DI를 통해 타깃 대신 클라이언트에게 주입되며, 클라이언트의 메서드 호출 대신 받아 타깃에 위임하며 그 과정에서 부가기능을 부여한다.

3. 스프링 AOP 구현

Spring AOP dependency 추가

maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

gradle

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

Spring AOP는 AnnotationAwareAspectJAutoProxyCreator 를 통해 Advisor 프록시 생성하는 역할을 하며 @Aspect를 보고 Advisor로 변환하여 저장하는 작업을 수행.

@Aspect
@Component
public class LogAspect {
...
}

위 코드처럼 @Aspect 어노테이션을 지정하면 Spring AOP 프록시 생성기에서 Adviosr로 변환하여 @Aspect Advisor 빌더 내부에 저장된다.

또한 프록시 생성기에서 Bean을 조회할 때 @Aspect가 빈을 추가하지 않기 때문에 @Bean 또는 @Component를 추가하여야 스프링 컨테이너가 빈을 등록된다.

@Aspect를 지정하고나면 Aspect 실행 시점을 지정할 수 있는 어노테이션들이 있다.

  • @Pointcut 포인트컷 설정

    • 포인트컷 지시자 종류
      • execution : 메서드 실행 조인 포인트를 매칭
        • 표현식 : execution([접근제한자 패턴] 리턴 값 타입패턴 [패키지 타입패턴.]메소드이름패턴 (파라미터 타입패턴 | "..", ...) [throws 예외 패턴])
      • within : 특정 타입 내의 조인 포인트를 매칭한다.
      • args : 인자가 주어진 타입의 인스턴스인 조인 포인트
      • this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
      • target : Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
      • @target : 실행 객체의 클래스에 주어진 타입의 어노테이션이 있는 조인 포인트
      • @within : 주어진 어노테이션이 있는 타입 내 조인 포인트
      • @annotation : 메서드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭
      • @args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 어노테이션을 갖는 조인 포인트
      • bean : 스프링 전용 포인트컷 지시자로 빈의 이름으로 포인트컷을 지정한다.
  • @Around

    • (@Before, @AfterReturning, @AferThrowing, @After) 어노테이션을 모두 포함
    • 메서드 호출 전후 작업 명시 가능
    • 조인 포인트 실행 여부 선택 가능
    • 반환값 자체를 조작 가능
    • 예외 자체를 조작 가능
    • 조인 포인트를 여러번 실행 가능(재시도)
  • @Before

    • 조인 포인트(Target Method) 수행 이전에 실행
  • @AfterReturning

    • 조인 포인트(Target Method) 수행 완료 후 실행
  • @AfterThrowing

    • Target Method에서 Exception이 발생할 경우 실행(에외 조작 불가능)

Ex) 로그 기록 AOP

Spring - AOP 총정리

  • @After
    • 메서드가 정상 반환하거나 예외로 끝나도 실행된다.

호출 시간을 재는 작은 예제

기존 예제처럼 싱글턴 Aspect의 필드에 시작 시간을 저장하면 두 요청이 동시에 실행될 때 값이 서로 덮인다. volatile은 가시성만 제공하고 요청별 값을 분리하지 않는다. 시작 시간은 @Around 메서드의 지역 변수로 둔다.

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimingAspect {
    private static final Logger log = LoggerFactory.getLogger(TimingAspect.class);

    @Around("execution(* com.example.order.OrderService.*(..))")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        long started = System.nanoTime();
        try {
            return joinPoint.proceed();
        } finally {
            long elapsed = System.nanoTime() - started;
            log.info("method={} elapsed_ns={}",
                    joinPoint.getSignature().getName(), elapsed);
        }
    }
}

proceed()를 정확히 한 번 호출하므로 대상 메서드의 반환값과 예외가 유지된다. finally는 예외가 나도 시간을 기록한다. 이 값에는 프록시 내부에서 실행한 작업이 들어가지만 클라이언트 네트워크 지연 전체가 포함되는 것은 아니다. 공개 API의 총 지연이 필요하면 HTTP 계층의 관측 지표와 함께 본다. 인수나 응답 전체를 로깅하면 비밀번호·개인정보가 새어 나갈 수 있어 메서드 이름과 시간만 기록했다.

sequenceDiagram
  participant Client as 호출자
  participant Proxy as Spring AOP 프록시
  participant Service as OrderService
  Client->>Proxy: service.placeOrder()
  Proxy->>Service: proceed()
  Service-->>Proxy: 결과 또는 예외
  Proxy-->>Client: 같은 결과 또는 예외

프록시를 통과하지 않는 같은 객체 내부 호출, 컨테이너가 만들지 않은 객체의 호출에는 이 Advice가 적용되지 않는다. @Transactional도 프록시 기반으로 쓰는 경우가 많아 같은 경계를 확인해야 한다. 로그가 안 찍히면 포인트컷 표현식, 빈 등록, 호출 경로 순으로 검사한다. 모든 메서드에 적용하는 넓은 포인트컷은 로그량과 지연을 키우므로 필요한 업무 경계만 고른다.

참고: Spring AOP 문서, 프록시 메커니즘.

같은 카테고리의 글