AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)은 공통된 기능(=횡단 관심사)을 분리하여 핵심 비즈니스 로직과 독립적으로 관리하기 위한 프로그래밍 패러다임입니다.
핵심 로직을 건드리지 않고도 공통 기능을 모듈화할 수 있습니다.
| 용어 | 설명 |
|---|---|
| 횡단 관심사(Cross-cutting Concern) | 모든 서비스에 걸쳐 중복되는 기능 (ex. 로깅, 인증) |
| 어드바이스(Advice) | 횡단 관심사를 구현한 실제 실행 코드 |
| 포인트컷(Pointcut) | 어드바이스가 적용될 지점(메서드 범위)을 지정하는 표현식 |
| 조인포인트(JoinPoint) | 어드바이스가 실행될 수 있는 구체적인 실행 지점 (메서드 실행 시점 등) |
| 타겟(Target) | 어드바이스가 적용되는 실제 객체 (ex. 서비스 클래스) |
| 에스팩트(Aspect) | 어드바이스 + 포인트컷을 합친 모듈 단위 |
| @Transactional | 내부적으로 AOP로 구현된 대표적인 어노테이션 |
| @Aspect | 클래스를 AOP 컴포넌트로 정의하는 어노테이션 |
| 어노테이션 | 실행 시점 | 설명 |
|---|---|---|
@Before | 메서드 실행 전 | 사전 작업 수행 |
@After | 메서드 실행 후 (성공/실패 모두) | 무조건 실행 |
@AfterReturning | 메서드 정상 종료 후 | 반환값 처리 가능 |
@AfterThrowing | 예외 발생 시 | 예외 로깅 등에 사용 |
@Around | 전/후/예외 모두 제어 | 실행 시간 측정, 전체 흐름 제어 가능 |
@Aspect
public class AopPractice {
private static final Logger log = LoggerFactory.getLogger(AopPractice.class);
// [1] 포인트컷: service 패키지 하위 전체 메서드
@Pointcut("execution(* com.spring.basic.service..*(..))")
public void serviceLayerPointCut() {}
// [2] 포인트컷: @TrackTime 어노테이션이 붙은 메서드
@Pointcut("@annotation(com.spring.basic.aop.TrackTime)")
public void trackTimePointCut() {}
// [3] 어드바이스 - 실행 전
@Before("serviceLayerPointCut()")
public void beforeMethod() {
log.info("@Before");
}
// [4] 어드바이스 - 정상 실행 후
@AfterReturning(pointcut = "serviceLayerPointCut()", returning = "result")
public void afterReturningMethod(Object result) {
log.info("@AfterReturning");
log.info("::: result : {}", result.getClass());
}
// [5] 어드바이스 - 예외 발생 시
@AfterThrowing(pointcut = "serviceLayerPointCut()", throwing = "exception")
public void afterThrowingMethod(Throwable exception) {
log.info("@AfterThrowing");
log.info("::: exception: {}", exception.getClass());
}
// [6] 어드바이스 - 무조건 실행
@After("serviceLayerPointCut()")
public void AfterMethod() {
log.info("@After");
}
// [7] 어드바이스 - Around 위 모든 경우에 사용 가능 ( ex)시간 측정 )
@Around("trackTimePointCut()")
public Object AroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object proceed = joinPoint.proceed();
log.info("@AfterReturning");
return proceed;
} catch (Throwable e) {
log.info("@AfterThrowing");
throw e;
} finally {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
log.info("::: executionTime: {}ms", executionTime);
}
}
}