횡단 관심사(Cross-Cutting Concerns)를 모듈화하기 위한 기술
여기서 횡단 관심사란, 애플리케이션의 핵심 비즈니스 로직과는 별개로 여러 모듈에 공통적으로 적용되는 기능을 의미합니다!
스프링에서 AOP는 프로그램의 여러 곳에서 반복적으로 사용되는 공통 기능(예: 로그 기록, 보안, 트랜잭션 관리)을 분리해서 코드를 더 깔끔하고 관리하기 쉽게 만드는 방법입니다.
예를 들어, 모든 메소드 호출 전에 로그를 남기고 싶다면, 일일이 모든 메소드에 로그 코드를 넣는 대신 AOP를 사용해 한 번만 정의해두면 됩니다!
1. Aspect 클래스 만들기
2. 포인트컷 설정
이렇게 정의할 수 있습니다.
Aspect Class는 공통으로 적용하고 싶은 기능을 정의하는 클래스 입니다. 예를 들어, "메소드가 실행되기 전에 로그를 남기자" 같은 기능을 이 클래스에 명시합니다.
다음으로 포인트컷 설정을 설정해야합니다. 이는 공통 기능을 "언제" 적용할지 정하도록 합니다. 예를 들어, "특정 패키지 안의 모든 메소드에 적용하자" 처럼 동작할 수 있습니다.
Aspect: 횡단 관심사를 모듈화한 것. 여러 개의 애드바이스(Advice)와 포인트컷(Pointcut)으로 구성됨
Advice: 실제로 처리해야 할 로직을 정의한 코드
Join Point: 프로그램 실행 중에 Advice가 적용될 수 있는 특정 시점
(example. 메서드 호출이나 예외 발생 시점 등)
Pointcut: Advice가 적용될 Join Point를 지정하는 표현식
Target Object: 애드바이스를 받는 객체입니다.
Introduction: 특정 타입의 메서드 구현을 추가하여 기존 클래스를 확장
AOP Proxy: AOP 기능을 구현하기 위해 생성된 프록시 객체
AspectJ 라이브러리 추가
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-aop'
@Aspect
@Component
public class LoggingAspect {
// 애드바이스 정의
}
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Method execution started");
}
}
@Aspect
public class LoggingAspect {
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("Method returned value is : " + result);
}
}
@Aspect
public class LoggingAspect {
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
public void logAfterThrowing(Throwable error) {
System.out.println("Exception : " + error);
}
}
@Aspect
public class LoggingAspect {
@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
System.out.println("Method execution finished");
}
}
@Aspect
public class LoggingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Method execution started");
Object result = joinPoint.proceed(); // 타겟 메서드 호출
System.out.println("Method execution finished");
return result;
}
}
@Aspect
public class LoggingAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {
// 포인트컷 정의
}
@Before("serviceLayer()")
public void logBefore() {
System.out.println("Method execution started");
}
}
AOP를 사용하여 로깅을 구현하는데, 이 때 메소드 실행 시간을 측정하여 같이 기록하기
package com.example.demo.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Around("execution(* com.example.demo.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 메소드 시작 시간 기록
long startTime = System.currentTimeMillis();
// 메소드 이름과 인수 로깅
logger.info("Entering method: {} with arguments: {}", joinPoint.getSignature(), joinPoint.getArgs());
Object result;
try {
// 타겟 메소드 실행
result = joinPoint.proceed();
} catch (Throwable throwable) {
// 예외가 발생한 경우 로깅
logger.error("Exception in method: {} with cause: {}", joinPoint.getSignature(), throwable.getCause() != null ? throwable.getCause() : "NULL");
throw throwable;
}
// 메소드 종료 시간 기록 및 소요 시간 계산
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
// 메소드 종료와 결과 로깅
logger.info("Exiting method: {} with result: {}. Time taken: {} ms", joinPoint.getSignature(), result, duration);
// 타겟 메소드의 결과 반환
return result;
}
}