"k8s 클러스터에 springboot application 배포후 모니터링" 연재를 다룰 예정인데, 그 과정에서 프로파일링 관련 기능이 필요할것 같아 이번에 포스팅하게 되었습니다. 이번 포스팅에서는 spring-aop와 애노테이션을 사용할것인데, 그전에 spring-aop와 annotation 관련 내용을 포스팅하게되었습니다.
간단히 메서드에 실행시간 출력하는 애노테이션을 추가했고, spring-aop를 통해 출력해보도록 하겠습니다.
이번 포스팅은 상황을 가정하고 그 상황에 필요한 기술을 맛보기로 다룹니다. 부하테스트로 timeout이 발생하는 이슈를 파악하고 로컬환경에서 api 요청에 따른 로직의 성능을 파악하기 위해 jaeger를 사용해서 프로파일링하는 것을 목표로 합니다.
다루는 내용
- spring-aop
- java annotation
- java reflection
환경
- java11
- springboot2.5.5
사전지식
- java reflection
- java annotation
- spring-aop
build.gradle
plugins {
id 'org.springframework.boot' version '2.5.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//테스트에서 lombok 사용
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
test {
useJUnitPlatform()
}
spring-aop
@Service 애노테이션을 붙인 Service 클래스 MyService를 하나 만들겁니다. 그리고 MyService에는 add, multiply 메서드를 만듭니다. 그리고 이 메서드에 LogExecutionTime이라는 이름의 애노테이션을 달겁니다. 그리고 이 메서드를 호출하게 되면 출력시간이 출력되는 예제를 만들어보겠습니다
MyService를 만듭니다
import org.springframework.stereotype.Service;
@Service
public class MyService {
@LogExecutionTime("Adding numbers")
public int add(int a, int b) throws InterruptedException {
Thread.sleep(1000); // Simulate some work
return a + b;
}
@LogExecutionTime("Multiplying numbers")
public int multiply(int a, int b) throws InterruptedException {
Thread.sleep(1500); // Simulate some work
return a * b;
}
}
LogExecutionTime이란 애노테이션을 만듭니다
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface LogExecutionTime {
String value() default "";
}
스프링 AOP를 구현하기 위해 @Aspect를 사용합니다
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Around("@annotation(logExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(logExecutionTime.value() + " executed in " + executionTime + "ms");
return proceed;
}
}
여기서 개념을 간략하게 설명하겠습니다
포인트컷: 포인트컷은 "어디에" 어드바이스를 적용할지를 정의합니다.
어드바이스: 어드바이스는 "무엇을" 할지, 즉 실제 실행될 로직을 정의합니다.
어드바이저: 하나의 포인트컷 + 하나의 어드바이스를 모듈화 한것, 스프링 AOP에서만 사용되는 특별한 용어
Aspect: 여러 어드바이스, 포인트컷을 모아놓은 더 큰 모듈
@Aspect: 이 Component스캔이 이 애노테이션이 붙은 클래스를 발견하면, 프록시를 생성합니다
이 프록시는 타겟 객체를 감싸고, 정의된 포인트컷에 따라 어드바이스를 실행합니다.
그래서 위 예시코드에서 포인트컷은 "어디에"
@Around("@annotation(logExecutionTime)")
그리고 어드바이스는 "무엇을"
public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(logExecutionTime.value() + " executed in " + executionTime + "ms");
return proceed;
}
그리고 이러한 어드바이저를 스프링 프레임워크에서 스프링 컨테이너 로딩 시점에 프록시 생성하는 것을 위빙이라고 합니다
스프링부트 main 함수
import hello.proxy.aop.annotation.MyService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication()
public class ProxyApplication {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context = SpringApplication.run(ProxyApplication.class, args);
MyService service = context.getBean(MyService.class);
System.out.println("Result of add: " + service.add(5, 3));
System.out.println("Result of multiply: " + service.multiply(5, 3));
}
}
출력결과
2024-09-03 11:52:15.690 INFO 46896 --- [ main] hello.proxy.ProxyApplication : Starting ProxyApplication using Java 11.0.13 on BE-YOBS.local with PID 46896 (/Users/nohys/Desktop/study/springboot/spring-core/proxy-start/build/classes/java/main started by nohys in /Users/nohys/Desktop/study/springboot/spring-core/proxy-start)
2024-09-03 11:52:15.691 INFO 46896 --- [ main] hello.proxy.ProxyApplication : No active profile set, falling back to default profiles: default
2024-09-03 11:52:15.964 INFO 46896 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2024-09-03 11:52:15.967 INFO 46896 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-09-03 11:52:15.967 INFO 46896 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.53]
2024-09-03 11:52:15.984 INFO 46896 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-09-03 11:52:15.984 INFO 46896 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 275 ms
2024-09-03 11:52:16.101 INFO 46896 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2024-09-03 11:52:16.106 INFO 46896 --- [ main] hello.proxy.ProxyApplication : Started ProxyApplication in 0.517 seconds (JVM running for 0.724)
Adding numbers executed in 1005ms
Result of add: 8
Multiplying numbers executed in 1501ms
Result of multiply: 15
이렇게 Aspect를 사용한 프로그래밍 방식으로 AOP라고 합니다
횡단 관심사를 깔끔하게 처리하기 위한 개발 방법론이며 이러한 기능때문에 스프링 프레임워크는 엔터프라이즈급 서비스를 구현하는데 특화된것 같습니다.
'스프링 프레임워크' 카테고리의 다른 글
Implementing Redis Cache in Spring Boot: From Basic Setup to Serialization (1) | 2025.03.17 |
---|---|
springboot에 redisson cache 적용하기 (0) | 2024.06.18 |
springboot에 CacheResolver로 MultipleCacheManager 구성하기 (0) | 2024.06.18 |
springboot에 JCache로 ehcache 로컬캐시 적용하기 (0) | 2024.06.17 |