BackEnd

[Spring] AOP

solanarey 2023. 8. 29. 21:54

AOP

AOP가 필요한 상황

  • 모든 메소드의 호출시간을 측정할 때
  • 공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern)
  • 회원 가입 시간, 회원 조회 시간을 측정하고 싶다면 ?

문제

  • 회원가입, 회원 조회 등에 시간을 측정하는 기능은 핵심 관심 사항이 아니다.
  • 시간을 측정하는 로직은 공통 관심 사항이다.
  • 시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어렵다.
  • 시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.
  • 시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다.



AOP 적용

AOP : Aspect Oriented Programming (관점 지향 프로그래밍)

  • 각 비즈니스 로직마다 시간을 측정하는 로직을 덧붙이지 않고 시간 측정 로직을 하나 만들어서
    원하는곳에 공통 관심 사항을 적용시킨다.
package com.hello.hellospring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class TimeTraceAop {

    // Config 클래스에서 Bean으로 등록하게 되면, Config 클래스의 timeTraceAop() 메서드도 AOP로 처리하게 되는데,
    // 그 메소드가 TimeTraceAop를 생성하는 코드라서 순환참조 문제가 발생한다.
    // 반면에 컴포넌트 스캔을 사용할때는 AOP의 대상이 되는 코드자체가 없기 때문에 오류가 발생하지 않는것이다.
    // Config에 Bean으로 직접 등록 해줄때는 아래와 같이 @Around 어노테이션에 Config클래스를 타겟이 아니라고 지정해주면 된다.
    @Around("execution(* com.hello.hellospring..*(..)) && !target(com.hello.hellospring.config.SpringConfig)")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("START: " + joinPoint.toString());
        try {
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
        }
    }
}
@Bean
public TimeTraceAop timeTraceAop() {
    return new TimeTraceAop();
}

AOP 클래스를 하나 만들고 @Aspect 어노테이션을 붙여줘야 한다.
@Around 어노테이션은 시간 측정 로직을 적용 시킬 비즈니스 로직이 있는 클래스들의 패키지 경로를 지정해주면 된다.

AOP는 정형화된 것이 아니기 때문에 업무상 보기 편하게 AOP 자신의 클래스에서 @Component을 명시해주는것 보다는
Config 클래스에서 빈으로 등록 해주는것이 좋다.




AOP 동작 방식

컨트롤러에서 실제 MemberService를 바로 호출하는게 아니라 프록시라는 가짜 MemberService를 만든다.
그 후 joinPoint.proceed()가 동작함으로서 실제 MemberService가 호출된다.