스프링에는 스프링 트라이앵글이라고 해서 3가지 중요성을 강조한다.
이번 포스팅에서는 이 중 하나의 축을 담당하고 있는 AOP 에 대해서 정리하고자 한다.
AOP는 Aspect-Oriented Programming의 약자로써 그대로 번역하면 관점-지향 프로그래밍을 의미한다.
간단히 말하면 특정 로직에서 핵심적인 관점과 부가적인 관점으로 나누고 그 관점을 기준으로 공통 관심사를 분리 및 모듈화하여 프로그래밍하겠다는 의미이다.
예제를 통해 알아보면 쉽다.
(1) 서비스 별 시간 측정
만일 특정 서비스의 경우, 성능측정을 위해 시간을 찍는다고 가정해보자. 그 서비스의 개수는 약 300개 정도로 구성되어있는 서버이다. 당신은 300개 서비스 클래스 및 메서드에 시간 측정 로직을 추가할 것인가?
(2) 로그
특정 url에 컨트롤러를 추가해서 단위 테스트 또는 통합 테스트를 진행한다고 가정해보자. 여기서 로그를 기록해서 문제점을 발견하고자 한다. 당신은 모든 서비스에 로그 출력 로직을 추가할 것인가..??
위와 같이 특정 메소드를 대상
으로 언제
어떠한 기능
을 구현해서 공통관심사 로직을 추가할 것인가? 이것이 AOP가 된다. 여기서 공통 관심사를 잘라냈다고 하여 크로스 컷팅(Cross-Cutting)이라고도 부른다.
AOP에서 사용되는 주요 용어는 다음과 같다.
스프링에서는 AspectJ나 스프링 AOP를 사용하여 프록시 객체를 생성하여 AOP를 제공해준다. 이때 Spring은 인터페이스는 JDK Dynamic Proxy를 통해 프록시 객체를 생성하고, 클래스는 CGLIB를 통해 프록시 객체를 생성해준다.
성능상 CGLIB가 이점이 많기 때문에 SpringBoot는 기본적으로 CGLIB를 통해 프록시 객체를 제공해준다.
또한, 이처럼 프록시 객체가 생성되는 과정을 Weaving이라고 한다.
스프링 AOP를 적용하는 방법은 어떤 시점에 AOP를 적용하는지에 따라 3가지가 존재한다.
아래는 추가하고자 하는 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;
}
}
아래에 LogExecutionTime(@Around에 벨류등록) 어노테이션을 생성해주자
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
아래는 LogExecutionTime 어노테이션을 활용한 pointcut이다
@LogExecutionTime
@ResponseBody
@GetMapping("/aopTest")
public String aopTest() {
log.info("aopTest");
return "ok";
}
실제로 localhost:8080/aopTest (호스트주소는임시) 로 접속해보면 우리가 원하는 Aspect 메소드가 실행되는 것을 확인 할 수 있다.