거꾸로 바라본 세상
Published 2023. 3. 31. 01:55
AOP(Aspect Oriented Programming) Spring
반응형

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에서 조인포인트는 메서드 실행 단계 에서만 동작한다.
  • PotinCut : Advice를 적용할 JoinPoint들의 위치를 선별한다. 스프링 AOP의 JoinPoint는 메소드실행 시점만 가능하다.
  • 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이 발생할 경우 실행(에외 조작 불가능)
  • @After
    • 모든 조인 포인트(Target Method)의 정상, 예외 동작과 무관하게 무조건 실행

 

 

Ex) 로그 기록 AOP

package com.study.aop.log;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

@Aspect
@Component
public class LogAspect {

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
    @Pointcut("execution(* com.study.aop.controller.*.*(..))")
    public void controller() {

    }

    private volatile long startTime = 0;

    @Before("controller()")
    public void startLog(JoinPoint joinPoint) {

        try {
            Signature signature = joinPoint.getSignature();
            RequestMapping requestMapping = getAnnotation(joinPoint, RequestMapping.class);

            this.startTime = System.currentTimeMillis();
            log.debug("requestMapping={}", requestMapping);
            String path = "";

            if (requestMapping != null) {

                String method = requestMapping.method()[0].name();
                if (method.equals("GET")) {
                    path = AopUtils.getAnnotation(joinPoint, GetMapping.class).value()[0];
                } else if (method.equals("POST")) {
                    path = AopUtils.getAnnotation(joinPoint, PostMapping.class).value()[0];
                } else if (method.equals("PUT")) {
                    path = AopUtils.getAnnotation(joinPoint, PutMapping.class).value()[0];
                } else {
                    path = AopUtils.getAnnotation(joinPoint, DeleteMapping.class).value()[0];
                }

                log.info("============================================================");
                log.info("## == Start ==");
                log.info("## == Package : {} ", signature.getDeclaringTypeName());
                log.info("## == Method : {}", signature.getName());
                log.info("## == URI : {}, Method : {}", path, method);
                log.info("============================================================");
            }
        } catch (Exception e) {
            log.error("startLog exception=", e);
        }
    }

    @AfterReturning("controller()")
    public void endLog(JoinPoint joinPoint) {
        try {
            Signature signature = joinPoint.getSignature();

            long endTime = System.currentTimeMillis();
            log.info("============================================================");
            log.info("## == END ==");
            log.info("## == Package :  " + signature.getDeclaringTypeName());
            log.info("## == Method : {}", signature.getName());
            log.info("## -> elapsed time: " + (endTime - this.startTime) + "ms");
            log.info("============================================================");
            log.info("");
        } catch (Exception e) {
            log.error("endLog Exception :" , e);
        }

    }

    @After("controller()")
    public void afterLog(JoinPoint joinPoint) {
        log.info("Start afterLog...");
    }

		protected <A extends Annotation> A getAnnotation(JoinPoint joinPoint, @Nullable Class<A> cls) {
        //call controller Method
        MethodSignature method = ((MethodSignature) joinPoint.getSignature());
        return AnnotationUtils.findAnnotation(method.getMethod(), cls);
    }
}

 

 

토비스프링 AOP

Spring - AOP 총정리

반응형
profile

거꾸로 바라본 세상

@란지에。

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!