1. AOP란?
**AOP(Aspect-Oriented Programming)**는 코드에서 공통적으로 반복되는 횡단 관심사를 핵심 비즈니스 로직에서 분리하는 프로그래밍 기법이다. AOP를 사용하면 로깅, 트랜잭션 관리, 메서드 실행 시간 측정 등 여러 곳에서 사용되는 부가기능을 한 곳에 모아 관리할 수 있다.
2. AOP 용어 정리
- Aspect(애스팩트): 프로그램에서 반복적으로 필요하지만, 모든 코드에 일일이 넣기 귀찮은 기능들을 한데 묶은 모듈이다. 예를 들어, 로그를 남기거나 보안 처리를 한 곳에 모아서 관리하는 것이라고 보면 된다.
- Advice(어드바이스): 실제로 실행될 부가기능이다. 예를 들어, 어떤 메소드가 실행될 때 로그를 남기는 구체적인 코드가 어드바이스이다.
- Pointcut(포인트컷): 어드바이스가 언제 실행될지를 정하는 규칙이다. 예를 들어, 특정 메소드나 클래스가 실행될 때만 어드바이스가 작동하도록 정하는 것이 포인트컷이다.
- JoinPoint(조인포인트): 어드바이스가 실제로 실행될 수 있는 지점을 의미한다. 메소드가 실행되거나 객체가 생성되는 순간 등이 조인포인트이다.
- Target(타겟): 어드바이스가 적용될 실제 객체이다. 예를 들어, 어떤 서비스 메소드에 어드바이스를 적용하면 그 메소드를 가지고 있는 객체가 타겟이 된다.
한마디로, Aspect는 여러 부가기능을 묶어서 필요한 곳에 자동으로 적용하게 만드는 기능이고, Advice는 그 부가기능의 실제 코드, Pointcut은 그 코드가 언제 실행될지를 정하는 규칙, JoinPoint는 실행 가능한 지점, Target은 그 기능이 적용되는 실제 객체이다.
3. AOP를 적용한 실습: 메서드 실행 시간 측정
문제 상황
메서드 실행 시간을 측정해야 하는데, 이를 각 서비스 메서드마다 일일이 추가하면 코드가 지저분해지고 유지보수성이 떨어진다. 이런 상황에서 AOP를 적용하면 실행 시간 측정 로직을 하나의 모듈로 관리할 수 있다.
구현 목표
AOP를 사용해 특정 패키지나 특정 어노테이션이 붙은 메서드들에 대해 실행 시간 측정을 적용하는 방법을 살펴보자.
4. 코드 설명
4-1. Aspect 클래스 전체 구조
package com.standard.sparta.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Slf4j
@Aspect
public class AspectPractice {
// 포인트컷 및 어드바이스들이 정의되어 있는 클래스
}
- 이 클래스는 @Aspect 어노테이션으로 AOP 역할을 하는 클래스임을 표시했다.
- @Slf4j는 로깅을 위한 롬복 어노테이션으로, 메서드 실행 시간을 로그로 출력하는데 사용된다.
4-2. 포인트컷(Pointcut) 정의
/**
* 포인트컷: 서비스 레이어의 모든 메서드에 적용
*/
@Pointcut("execution(* com.standard.sparta.service..*(..))")
private void serviceLayer() {}
/**
* 포인트컷: @TrackTime 어노테이션이 붙은 메서드에만 적용
*/
@Pointcut("@annotation(com.standard.sparta.annotation.TrackTime)")
private void trackTimeAnnotation() {}
- serviceLayer: com.standard.sparta.service 패키지 내의 모든 메서드를 대상으로 하는 포인트컷이다. 패키지 경로를 설정하여 특정 클래스나 메서드에 어드바이스를 적용할 수 있다.
- execution(* com.standard.sparta.service..*(..))는 service 패키지 내에 있는 모든 메서드에 적용하겠다는 의미이다.
- trackTimeAnnotation: @TrackTime이라는 커스텀 어노테이션이 붙은 메서드에 대해서만 어드바이스를 적용하는 포인트컷이다.
@TrackTime Annotation 만들기
더보기
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 TrackTime {
}
4-3. 어드바이스(Advice) 설명
1. @Around 어노테이션을 사용한 어드바이스
/**
* 어드바이스: @TrackTime 어노테이션 기반으로 메서드 실행 시간을 측정
*/
@Around("trackTimeAnnotation()")
public Object adviceAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis(); // 실행 시간 측정 시작
try {
// 메서드 실행
Object result = joinPoint.proceed();
return result;
} catch (Exception ex) {
log.error("Exception in method: {}", joinPoint.getSignature(), ex);
throw ex; // 예외 다시 던짐
} finally {
// 실행 시간 측정 종료 및 로그 출력
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
log.info("Execution time of {}: {} ms", joinPoint.getSignature(), executionTime);
}
}
- 어노테이션 기반 실행 시간 측정
- 이 어드바이스는 @TrackTime 어노테이션이 붙은 메서드에 대해 적용된다.
- **joinPoint.proceed()**를 통해 원래 메서드를 실행한 후, 실행 시간이 끝난 후에 측정된 시간을 로그로 출력한다.
- 예외가 발생할 경우, catch 블록에서 처리한 후 다시 예외를 던진다.
2. @AfterReturning 어드바이스
/**
* 어드바이스: 서비스 패키지 기반으로 메서드 실행 시간을 측정
*/
@Around("serviceLayer()")
public Object advicePackageMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis(); // 실행 시간 측정 시작
try {
// 메서드 실행
Object result = joinPoint.proceed();
return result;
} catch (Exception ex) {
log.error("Exception in method: {}", joinPoint.getSignature(), ex);
throw ex; // 예외 다시 던짐
} finally {
// 실행 시간 측정 종료 및 로그 출력
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
log.info("Execution time of {}: {} ms", joinPoint.getSignature(), executionTime);
}
}
- 패키지 기반 실행 시간 측정
- 이 어드바이스는 com.standard.sparta.service 패키지 내의 모든 메서드에 대해 실행 시간을 측정한다.
- **joinPoint.proceed()**를 호출하여 원래 메서드를 실행하고, 실행 시간이 끝나면 측정된 시간을 로그로 출력한다.
어드바이스 종류
더보기
- @Around: '핵심기능' 수행 전과 후 (@Before + @After)
- @Before: '핵심기능' 호출 전 (ex. Client 의 입력값 Validation 수행)
- @After: '핵심기능' 수행 성공/실패 여부와 상관없이 언제나 동작 (try, catch 의 finally() 처럼 동작)
- @AfterReturning: '핵심기능' 호출 성공 시 (함수의 Return 값 사용 가능)
- @AfterThrowing: '핵심기능' 호출 실패 시. 즉, 예외 (Exception) 가 발생한 경우만 동작 (ex. 예외가 발생했을 때 개발자에게 email 이나 SMS 보냄)
4-4. 기타 어드바이스들
1. @Before 어드바이스
/**
* 어드바이스: 메서드 실행 전에 특정 로직을 실행
*/
@Before("serviceLayer()")
public void beforeMethod() {
log.info("::: BEFORE 메서드 실행 :::");
}
- 이 어드바이스는 메서드 실행 전에 동작하며, 서비스 레이어의 모든 메서드에 대해 적용된다. 메서드 실행 전에 특정 작업(로그 출력 등)을 하고 싶을 때 사용된다.
2. @AfterReturning 어드바이스
/**
* 어드바이스: 메서드가 정상적으로 반환된 후에 실행
*/
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void afterReturningMethod(Object result) {
log.info("::: AFTER RETURNING ::: 메서드 실행 완료. 반환 값: {}", result);
}
- 메서드가 성공적으로 실행되고 반환된 후에 실행되는 어드바이스다. 정상적인 실행 결과를 받아 후처리할 때 유용하다.
3. @AfterThrowing 어드바이스
/**
* 어드바이스: 메서드 실행 중 예외가 발생했을 때 실행
*/
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void afterThrowingMethod(Throwable ex) {
log.info("::: AFTER THROWING ::: 메서드 실행 중 예외 발생: {}", ex.getMessage());
}
- 메서드 실행 중 예외가 발생했을 때만 실행되는 어드바이스다. 예외 발생 시 특정 작업을 처리할 수 있다.
4. @After 어드바이스
/**
* 어드바이스: 메서드 실행 후 항상 실행
*/
@After("serviceLayer()")
public void afterMethod() {
log.info("::: AFTER 메서드 실행 :::");
}
- 메서드가 정상적으로 실행되든, 예외가 발생하든 상관없이 항상 실행되는 어드바이스다. 주로 리소스 해제 등의 후처리 작업에 유용하다.
5. @Around 어드바이스
@Around("trackTimeAnnotation()")
public Object adviceAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 메서드 실행 전 로그
log.info("Method {} is starting", joinPoint.getSignature().getName());
try {
// 메서드 실행
Object proceed = joinPoint.proceed();
// 메서드 실행 후 로그
log.info("Method {} successfully finished", joinPoint.getSignature().getName());
return proceed;
} finally {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
// 메서드 실행 시간 로깅
log.info("Method {} executed in {} ms", joinPoint.getSignature().getName(), executionTime);
}
}
- @Before + @After를 합쳐 놓은 것처럼 메서드 실행 전, 후 작동을 한다.
5. 결론
AOP를 사용하면 반복되는 코드를 분리하여 핵심 비즈니스 로직과 부가기능을 명확히 분리할 수 있다. 이번 실습에서는 메서드 실행 시간을 측정하는 로직을 AOP로 구현했고, 포인트컷을 사용해 특정 패키지나 어노테이션이 붙은 메서드에 적용하는 방법을 살펴봤다.
'SPRING&BOOT' 카테고리의 다른 글
PUT과 PATCH 메서드 (1) | 2024.09.27 |
---|---|
Spring Boot에서 JWT 인증 필터와 ArgumentResolver 활용하기 (1) | 2024.09.20 |
필터 (0) | 2024.09.03 |
캡슐화(Encapsulation) (0) | 2024.08.28 |
API 명세서란? API 명세서 작성의 중요성과 방법 (0) | 2024.08.18 |