안녕하세요 오늘은 Spring AOP에 대해 알아보겠습니다.
**Aspect-Oriented Programming (AOP)**는 소프트웨어 개발에서 관심사 분리(Separation of Concerns)를 달성하기 위한 프로그래밍 패러다임입니다. Spring AOP는 Spring Framework에서 AOP를 구현한 모듈로, 횡단 관심사(cross-cutting concerns)를 애플리케이션 로직과 분리하여 모듈화할 수 있게 해줍니다.
1. AOP의 주요 개념
AOP에서는 비즈니스 로직과 관련된 코드 외에
보안, 로깅, 트랜잭션 관리, 예외 처리 등과 같은 공통적인 기능(횡단 관심사)을
별도의 모듈로 분리할 수 있습니다.
AOP의 주요 개념은 다음과 같습니다:
1.1 Aspect (애스펙트)
- 정의: 애스펙트는 횡단 관심사를 모듈화한 것. 쉽게 말해, 로깅이나 트랜잭션 관리와 같은 공통 기능을 정의하는 모듈입니다.
- 역할: 애스펙트는 주로 여러 클래스에서 반복적으로 사용되는 기능을 분리하여, 중복 코드를 제거하고 관심사의 분리를 돕습니다.
1.2 Join Point (조인 포인트)
- 정의: 조인 포인트는 애스펙트가 실행될 수 있는 프로그램의 특정 위치를 의미합니다. Spring AOP에서는 메서드 실행이 조인 포인트로 간주됩니다.
- 예시: 메서드 호출, 객체 생성, 예외 발생 등이 조인 포인트가 될 수 있습니다.
1.3 Advice (어드바이스)
- 정의: 어드바이스는 실제 애스펙트에서 수행되는 동작을 정의한 것입니다. 조인 포인트에서 실행되는 코드를 의미합니다.
- Before Advice: 조인 포인트 전에 실행됩니다.
- After Advice: 조인 포인트 후에 실행됩니다.
- Around Advice: 조인 포인트 전후에 모두 실행되며, 메서드 호출 자체를 제어할 수 있습니다.
- After Returning Advice: 메서드가 정상적으로 실행된 후에 실행됩니다.
- After Throwing Advice: 메서드 실행 중 예외가 발생했을 때 실행됩니다.
1.4 Pointcut (포인트컷)
- 정의: 포인트컷은 어드바이스가 적용될 조인 포인트를 지정하는 표현식입니다. 즉, 애플리케이션의 어떤 부분에서 어드바이스를 실행할지를 정의합니다.
- 역할: 포인트컷은 특정 메서드나 클래스에만 어드바이스를 적용할 수 있도록 설정합니다.
1.5 Target (타겟)
- 정의: 타겟은 어드바이스가 적용되는 실제 객체입니다.
- 역할: 비즈니스 로직을 구현하는 클래스가 타겟이 되며, AOP는 이 타겟의 동작을 변경하거나 확장할 수 있습니다.
1.6 Weaving (위빙)
- 정의: 위빙은 애스펙트를 타겟 객체에 적용하는 과정입니다. 컴파일 타임, 로드 타임, 런타임에 위빙이 이루어질 수 있습니다.
- Spring AOP: Spring AOP는 주로 런타임 위빙을 사용합니다. 이는 프록시 기반으로 동작하여 런타임에 어드바이스가 적용된 프록시 객체를 생성합니다.
2. Spring AOP의 동작 방식
Spring AOP는 프록시 패턴을 사용하여 AOP 기능을 구현합니다.
프록시 객체는 실제 타겟 객체의 메서드를 호출할 때,
어드바이스를 적용하는 역할을 합니다.
프록시 기반 AOP:
Spring AOP는 기본적으로 JDK 동적 프록시 또는
CGLIB 프록시를 사용하여 런타임에 프록시 객체를 생성합니다.
프록시 객체는 타겟 객체의 메서드를 호출할 때, 정의된 어드바이스를 실행합니다.
3. Spring AOP 예제
다음은 Spring AOP를 사용하여 타임아웃을 구현한 예제입니다:
의존성 추가
Spring AOP를 사용하기 위해 spring-boot-starter-aop 의존성을 추가합니다:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.1 Timeout 애너테이션 정의
먼저, Timeout 애너테이션을 정의합니다. 이 애너테이션은 메서드에 대해 타임아웃을 설정할 수 있도록 해줍니다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timeout {
int value() default 5; // 타임아웃 기본값 5초
}
- @Target(ElementType.METHOD): 이 애너테이션이 메서드에 적용될 수 있음을 나타냅니다.
- @Retention(RetentionPolicy.RUNTIME): 이 애너테이션이 런타임 동안 유지된다는 것을 나타냅니다.
- int value() default 5;: 타임아웃 시간을 초 단위로 설정합니다. 기본값은 5초입니다.
3.2 TimeoutAspect 클래스 (AOP를 사용한 타임아웃 적용)
이 클래스는 Timeout 애너테이션이 적용된 메서드에 대해
타임아웃 기능을 적용하는 AOP(Aspect-Oriented Programming) 클래스입니다.
@Aspect
@Component
@Log4j2
public class TimeoutAspect {
private final ExecutorService executorService = Executors.newCachedThreadPool();
@Around("@annotation(timeoutAnnotation)")
public Object applyTimeout(ProceedingJoinPoint joinPoint, Timeout timeoutAnnotation) throws Throwable {
log.info("[TIMEOUT] START");
Callable<Object> task = () -> {
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
throw new ExecutionException(throwable);
}
};
Future<Object> future = executorService.submit(task);
try {
Object o = future.get(timeoutAnnotation.value(), TimeUnit.SECONDS);
log.info("[TIMEOUT] END");
return o;
} catch (TimeoutException e) {
log.error("[TIMEOUT] Method {} execution timed out", joinPoint.getSignature().getName());
future.cancel(true);
throw new RuntimeException();
} catch (ExecutionException | InterruptedException e) {
log.error("[ERROR] Method {} execution failed", joinPoint.getSignature().getName(), e);
throw e.getCause();
}
}
}
- @Aspect: 이 클래스가 AOP 관점(Aspect)임을 나타냅니다.
- @Component: 이 클래스가 Spring의 컴포넌트로 관리됨을 나타냅니다.
- @Around("@annotation(timeoutAnnotation)"): Timeout 애너테이션이 적용된 메서드 실행 전후를 감싸서 타임아웃 로직을 적용합니다.
- ExecutorService executorService = Executors.newCachedThreadPool(): 스레드 풀을 생성하여 메서드 실행을 비동기로 처리합니다.
- Callable<Object> task = () -> { ... }: 메서드 실행을 Callable로 래핑하여 비동기 실행이 가능하게 합니다.
- future.get(timeoutAnnotation.value(), TimeUnit.SECONDS): 지정된 타임아웃 내에 메서드가 완료되기를 기다립니다. 타임아웃을 초 단위로 설정합니다.
- TimeoutException: 타임아웃이 발생했을 때 발생하는 예외입니다.
- ExecutionException 및 InterruptedException: 메서드 실행 중 발생한 예외를 처리합니다.
3. uploadImageFile 메서드 (타임아웃 적용된 메서드)
**제가 적용 시킬 메서드 부분입니다.
이 메서드는 파일을 업로드하는 기능을 수행하며,
@Timeout(5) 애너테이션으로 인해 5초 타임아웃이 적용됩니다.
@Timeout(5)
public String uploadImageFile(String now, MultipartFile file){
if (file == null || file.isEmpty()) {
return null;
}
try {
Path datePath = createDatePath(now);
String fileName = generateImageFileName(file);
Path targetLocation = datePath.resolve(fileName);
if(!Files.isDirectory(datePath)){
Files.createDirectories(datePath);
}
// 대상 파일이 이미 존재하는지 확인
while (Files.exists(targetLocation)) {
// 이미 존재하는 파일이면 새 값으로 파일 이름을 생성하여 다시 시도
fileName = generateImageFileName(file);
targetLocation = datePath.resolve(fileName);
}
Files.copy(file.getInputStream(), targetLocation);
log.info("Create Resource: {}", targetLocation.toString());
return fileName;
} catch (IOException e) {
log.error("[FILE] File save Error: {}", e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
- @Timeout(5): 이 메서드에 5초의 타임아웃을 적용합니다.
- 파일을 업로드하는 기능을 수행하며, 타임아웃이 적용됩니다. 5초 내에 작업이 완료되지 않으면 예외가 발생합니다.
- 참고로 aop는 스프링 빈 바로 밑 직접적으로 적용되야 동작합니다. 예를 들어 @Service가 적용된 class 안에 getImage 메서드안 uploadImageFile 메서드에 붙어있는 @Timeout(5)는 적용이 되지 않습니다.
4. 결론
이 구현은 AOP와 애너테이션을 사용하여 메서드 실행에 타임아웃을 적용하는 강력한 방법을 제공합니다.
이를 통해 메서드가 예기치 않게 오랜 시간 실행되는 것을 방지하고,
시스템의 안정성을 유지할 수 있습니다.
이 방식은 특히 파일 업로드, 네트워크 호출, 데이터베이스 작업 등
실행 시간이 예측할 수 없는 작업에 매우 유용합니다.
@Timeout 애너테이션을 통해 각 메서드마다 원하는 타임아웃을 설정할 수 있으며,
TimeoutAspect를 통해 이러한 타임아웃을 일관성 있게 관리할 수 있습니다.
Spring AOP는 횡단 관심사를 효과적으로 분리하여 코드의 유지보수성을 높이는 데 큰 도움이 됩니다.
이를 통해 비즈니스 로직에 집중할 수 있으며, 반복적인 코드 작성을 줄이고,
코드를 모듈화하여 더 명확하게 관리할 수 있습니다.
Spring AOP는 로깅, 트랜잭션 관리, 보안 등 다양한 공통 기능을 손쉽게 구현할 수 있는 강력한 도구입니다.
감사합니다.
'스프링부트' 카테고리의 다른 글
스프링 부트 애플리케이션의 성능 최적화: 캐싱, 프로파일링 등 다양한 방법 (0) | 2024.08.26 |
---|---|
스프링 부트의 10가지 필수 기능: 강력하고 확장 가능한 애플리케이션 구축 (0) | 2024.08.26 |
Java 및 Spring의 최신 탐색 : Java 17 및 Spring 3 가이드 (0) | 2024.02.17 |
액세스 및 리프레쉬 토큰을 사용한 JWT 전략 구현 (0) | 2024.02.17 |
webFlux 사용기 (1) | 2023.12.27 |
댓글