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가 호출된다.