Spring Boot - AOP로 메서드 실행 시간 측정

gzip·2023년 6월 3일
0

Spring

목록 보기
3/3
post-thumbnail

AOP를 활용하여 메서드의 실행 시간을 측정하기

Spring Boot: 3.1.0


구현

실행 시간을 측정하고 싶은 메서드에 @TimeTrace라는 어노테이션을 추가하면 동작되도록하자.


build.gradle.kts

implementation("org.springframework.boot:spring-boot-starter-aop")

spring-boot-starter-aop가 필요하다. 없으면 추가하자.


TimeTrace.java

package com.example.demo.config.aop;  
  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface TimeTrace {  
}
  • 타겟은 메서드
  • 어노테이션이 런타임까지 유지되도록

TimeTraceAspect.java

package com.example.demo.config.aop;  
  
import lombok.extern.log4j.Log4j2;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.Around;  
import org.aspectj.lang.annotation.Aspect;  
import org.aspectj.lang.annotation.Pointcut;  
import org.springframework.stereotype.Component;  
import org.springframework.util.StopWatch;  
  
@Log4j2  
@Component  
@Aspect  
public class TimeTraceAspect {  
  
	// @Pointcut("execution(* com.example.demo.repository..*(..))")  
	@Pointcut("@annotation(com.example.demo.config.aop.TimeTrace)")  
	private void timeTracePointcut() {  
	}  
	  
	@Around("timeTracePointcut()")  
	public Object traceTime(ProceedingJoinPoint joinPoint) throws Throwable {  
		StopWatch stopWatch = new StopWatch();  
		  
		try {  
			stopWatch.start();  
			return joinPoint.proceed(); // 실제 타겟 호출
		} finally {  
			stopWatch.stop();  
			log.debug("{} - Total time = {}s",  
					joinPoint.getSignature().toShortString(), 
					stopWatch.getTotalTimeSeconds());  
		}  
	}  
}
  • PCD(Pointcut Designator)@annotaion을 사용
    - TimeTrace 어노테이션을 가지고 있는 메서드를 타겟으로 함
  • Advice는 실제 타겟 호출 전, 후에 로직이 있으므로 @Around를 사용
    - 실제 타겟 호출 전에 StopWatch를 시작, 마지막에 StopWatch를 종료하고 로그를 남기는 로직

테스트

// controller
@RestController  
@RequiredArgsConstructor  
public class HelloController {  
	private final HelloService helloService;  
	  
	@TimeTrace  
	@GetMapping("hello")  
	public void hello() throws InterruptedException {  
		helloService.hello();  
	}
}

// service
@Service  
public class HelloServiceImpl implements HelloService {  
  
	@TimeTrace  
	@Override  
	public void hello() throws InterruptedException {  
		Thread.sleep(1000);  
	}  
}

결과

... HelloServiceImpl.hello() - Total time = 1.005074833s 
... HelloController.hello() - Total time = 1.009257125s 

동작 원리

메서드에 @TimeTrace 어노테이션만 추가 했을 뿐인데 어떻게 프록시를 적용할 수 있었을까?


Spring Boot - Auto Configuration

Spring Boot는 Auto-Configuration 기능이 있다.

// ...
@EnableAutoConfiguration  
// ...
public @interface SpringBootApplication {
  • @SpringBootApplication 어노테이션을 보면 @EnableAutoConfiguration이 포함되어 있다.

우리가 라이브러리를 사용할 때, 사용자 입장에서는 어떤 객체들을 스프링 빈으로 등록해야 하는지 모른다. 사용자들이 사용할 라이브러리의 스프링 빈을 직접 등록해서 사용해야한다면 번거로울 것이다.

자동 구성이 적용된 라이브러리는 추가만하면 스프링 빈들이 자동으로 등록된다. 예를 들면 우리가 Spring Data JPA를 사용할 때 DataSource, TransationManager 같은 빈들을 따로 등록하지 않아도 자동으로 등록된다.

마찬가지로 spring-boot-starter-aop를 추가하면 AnnotationAwareAspectJAutoProxyCreator라는 빈 후처리기가 자동으로 스프링 빈에 등록된다.

빈 후처리기는 객체를 스프링 빈에 등록하기 직전 조작을 할 수 있게 한다.


AnnotationAwareAspectJAutoProxyCreator

역할

  • @Aspect 어노테이션이 있는 스프링 빈을 BeanFactoryAspectJAdvisorBuilder를 통해 Advisor를 생성하고 저장함
  • Advisor의 정보를 기반으로 적용 대상을 확인하고 프록시를 생성함

전체적인 프록시 적용 과정

  1. 애플리케이션 시작 시 스프링 빈 대상이 되는 객체를 생성
  2. 스프링 컨테이너에 빈이 등록 되기 전에 AnnotationAwareAspectJAutoProxyCreator(빈 후처리기)에 전달됨
  3. AnnotationAwareAspectJAutoProxyCreator 로직
    1) Advisor 생성
    - @Aspect 어노테이션이 있는 스프링 빈을 BeanFactoryAspectJAdvisorBuilder를 통해 Advisor를 생성하고 저장함
    2) Advisor 조회
    - 스프링 컨테이너에서 Advisor 빈 조회
    - BeanFactoryAspectJAdvisorBuilder에 저장된 Advisor 조회
    3) 프록시 대상 확인
    - AdvisorPointcut을 사용해서 해당 객체가 프록시를 적용할 대상인지 아닌지 확인
    - (메서드도 확인)
    4) 프록시 생성 및 반환
    - 프록시 적용 대상이면 프록시를 생성해서 반환, 적용 대상이 아니면 원래 객체를 반환
  4. 반환 받은 객체를 스프링 컨테이너에 저장

공부한 내용으로 틀린 정보가 있을 수 있습니다.

0개의 댓글