메서드의 실행시간이 얼마나 걸리는지 확인하고 싶었습니다.
실행 시간을 측정하고 싶은 메서드에 @TimeTrace
라는 어노테이션을 만들어서,
이를 Spring의 AOP 기능으로 공통모듈화 시켜보았습니다.
TimeTrace 어노테이션
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeTrace {
}
TimeTraceAspect.java
@Slf4j
@Component
@Aspect
public class TimeTraceAspect {
@Pointcut("@annotation(com.wala.api.infra.aop.TimeTrace)")
public void timeTracePointcut() {
}
@Around("timeTracePointcut()")
public Object timeTrace(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
return joinPoint.proceed(); // 실제 타겟 호출
} finally {
long end = System.currentTimeMillis();
long timeMs = end - start;
log.info("{} - total time = {} ms", joinPoint.getSignature().toShortString(), timeMs);
}
}
}
TimeTrace
어노테이션을 가지고 있는 메서드를 타겟으로 함Advice
는 실제 타겟 호출 전, 후에 로직이 있으므로 @Around
를 사용// 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 어노테이션만 -> 프록시를 적용!
@SpringBootApplication
어노테이션을 안에 @EnableAutoConfiguration
이 포함되어 있다.
개발자들이 사용할 라이브러리의 스프링 Bean을 직접 등록해서 사용해야한다면 번거로울 것이다.
⭐ 이 어노테이션이 자동으로 Bean으로 등록해준다.
그리고 Aop 빈 즉, @TimeTrace 같은 어노테이션을 Bean으로 등록할 때는 AnnotationAwareAspectJAutoProxyCreator
라는 빈 후처리기가 자동으로 등록해준다!
✅ 빈 후처리기
객체를 스프링 빈에 등록하기 직전 조작을 할 수 있게 한다.
✅ AnnotationAwareAspectJAutoProxyCreator이란?
@Aspect 어노테이션이 있는 스프링 빈을 BeanFactoryAspectJAdvisorBuilder를 통해 Advisor를 생성하고 저장함
Advisor의 정보를 기반으로 적용 대상을 확인하고 프록시를 생성함
이런 AOP 방식으로 여러가지 메서드마다 공통모듈을 적용시킬 수 있다.
@CPUUsage
, @MemoryUsage
어노테이션을 메서드로 붙여서 확인!
CPUUsage
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CPUUsage {
}
MemoryUsage
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MemoryUsage {
}
MonitoringAspect
@Slf4j
@Component
@Aspect
public class MonitoringAspect {
@Pointcut("@annotation(CPUUsage) || @annotation(MemoryUsage)")
public void monitorPointcut() {}
@Around("monitorPointcut()")
public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
double cpuUsage = operatingSystemMXBean .getSystemCpuLoad() * 100; // CPU 사용량
log.info("CPU Usage: " + String.format("%.2f", cpuUsage) + "%");
Runtime runtime = Runtime.getRuntime();
double totalMemory = runtime.totalMemory(); // 현재 시스템의 총 메모리
double freeMemory = runtime.freeMemory(); // 현재 시스템의 사용 가능한 메모리
double usedMemory = totalMemory - freeMemory; // 현재 시스템에서 사용 중인 메모리
double memoryUsage = (usedMemory / totalMemory) * 100;
log.info("Memory Usage: " + String.format("%.2f", memoryUsage) + "%");
log.info("usedMemory: " + String.format("%.2f", (usedMemory / (1024 * 1024))) + " MB");
return joinPoint.proceed();
}
}