Spring - (15) : AOP

­이승환·2021년 12월 10일
0

spring

목록 보기
13/26

복습


스프링에는 스프링 트라이앵글이라고 해서 3가지 중요성을 강조한다.

  1. IoC / DI
  2. PSA
  3. AOP

이번 포스팅에서는 이 중 하나의 축을 담당하고 있는 AOP 에 대해서 정리하고자 한다.

AOP는 Aspect-Oriented Programming의 약자로써 그대로 번역하면 관점-지향 프로그래밍을 의미한다.
간단히 말하면 특정 로직에서 핵심적인 관점과 부가적인 관점으로 나누고 그 관점을 기준으로 공통 관심사를 분리 및 모듈화하여 프로그래밍하겠다는 의미이다.

예제를 통해 알아보면 쉽다.

예시

(1) 서비스 별 시간 측정

만일 특정 서비스의 경우, 성능측정을 위해 시간을 찍는다고 가정해보자. 그 서비스의 개수는 약 300개 정도로 구성되어있는 서버이다. 당신은 300개 서비스 클래스 및 메서드에 시간 측정 로직을 추가할 것인가?

(2) 로그

특정 url에 컨트롤러를 추가해서 단위 테스트 또는 통합 테스트를 진행한다고 가정해보자. 여기서 로그를 기록해서 문제점을 발견하고자 한다. 당신은 모든 서비스에 로그 출력 로직을 추가할 것인가..??

위와 같이 특정 메소드를 대상으로 언제 어떠한 기능을 구현해서 공통관심사 로직을 추가할 것인가? 이것이 AOP가 된다. 여기서 공통 관심사를 잘라냈다고 하여 크로스 컷팅(Cross-Cutting)이라고도 부른다.

용어

AOP에서 사용되는 주요 용어는 다음과 같다.

  • Target : 부가기능(Aspect)을 적용할 대상,
  • Aspect : 시간을 측정하는 기능처럼 흩어진 관심사를 분리하여 모듈화 한 것, Aspect는 부가될 기능을 정의한 Advice와 Advice를 어디에 적용할지를 결정하는 PointCut을 함께 가지고 있음
  • Advice : 실질적으로 Aspect가 '무엇'을 '언제' 수행할지를 정의
  • JoinPoint : Advice가 적용될 위치, 적용할 위치는 굉장히 다양함
  • PointCut : Advice가 적용될 위치를 보다 구체적으로 정의한 것, JoinPoint는 Advice를 적용할 수 있는 위치의 모음이라면 PointCut은 그중 실제로 적용할 위치를 의미

설명 및 예제

스프링에서는 AspectJ나 스프링 AOP를 사용하여 프록시 객체를 생성하여 AOP를 제공해준다. 이때 Spring은 인터페이스는 JDK Dynamic Proxy를 통해 프록시 객체를 생성하고, 클래스는 CGLIB를 통해 프록시 객체를 생성해준다.

성능상 CGLIB가 이점이 많기 때문에 SpringBoot는 기본적으로 CGLIB를 통해 프록시 객체를 제공해준다.

또한, 이처럼 프록시 객체가 생성되는 과정을 Weaving이라고 한다.

스프링 AOP를 적용하는 방법은 어떤 시점에 AOP를 적용하는지에 따라 3가지가 존재한다.

  • 컴파일 시점 : .java 파일이 Java 컴파일러에 의해 .class 파일로 컴파일될 때 AOP를 적용, AspectJ
  • 로딩 시점 : .class 파일이 Class Loader에 의해 메모리에 로딩될 때 AOP를 적용, AspectJ
  • 런타임 시점 : 프로그램이 실행될 때 AOP를 적용, 스프링 AOP

아래는 추가하고자 하는 Aspect(모듈) 이다.

@Slf4j
@Aspect
@Component
public class LogAspect {

    @Around("@annotation(LogExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Object proceed = joinPoint.proceed();

        stopWatch.stop();
        log.info(stopWatch.prettyPrint());

        return proceed;
    }
}
  • @Aspect을 통해 Aspect라는 것을 알려주고 @Component을 통해 빈으로 등록해준다.
  • @Around는 Advice를 의미한다.
  • @Before, @After 등 다양한 Advice가 존재하며 @Around는 타겟 메서드를 감싸서 타겟 메서드 호출 전/후에 Advice 기능을 수행한다.
  • @annotation(LogExecutionTime)은 PointCut을 의미하며 annotation은 지정자, LogExecutionTime은 타겟 명세라고 한다.
  • 즉, LogExecutionTime 어노테이션이 붙은 애들한테 시간 측정 AOP를 적용하겠다는 의미이다.
  • joinPoint는 AOP로 감싼 실제 메서드를 생각하면 된다.

아래에 LogExecutionTime(@Around에 벨류등록) 어노테이션을 생성해주자

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
  • @Target은 Target을 의미하며 메서드에만 적용하겠다고 설정
  • @Retention은 RUNTIME 시에만 적용하겠다는 의미이다.

아래는 LogExecutionTime 어노테이션을 활용한 pointcut이다

@LogExecutionTime
@ResponseBody
@GetMapping("/aopTest")
    public String aopTest() {
    log.info("aopTest");
    return "ok";
}

실제로 localhost:8080/aopTest (호스트주소는임시) 로 접속해보면 우리가 원하는 Aspect 메소드가 실행되는 것을 확인 할 수 있다.

profile
Mechanical & Computer Science

0개의 댓글