Java/Spring
AOP(Aspect-Oriented Programming)
Z_Z
2025. 5. 30. 14:30
반응형
AOP(Aspect-Oriented Programming)
핵심 비즈니스 로직과 공통 관심 사항(로깅, 보안, 트랜잭션, 예외 처리 등)을 분리하기 위한 프로그래밍 패러다임이다.
AOP는 관심사를 모듈화해서 코드 중복을 줄이고 유지보수를 쉽게 만들기 위한 개념이다.
예를 들어, 모든 서비스 로직에 로깅을 넣는다고 할 때 각 메서드마다 System.out.println() 을 직접 쓰는건
비효율적이다. AOP를 쓰면 한 곳에서 정의하고 자동으로 설정한 모든 메서드에 적용할 수 있다.
🔧 어디에 사용하는가?
상황 | 설명 |
🔍 로깅(logging) | 메서드 실행 전/후 시간, 파라미터, 결과값 등을 기록할 때 |
🔒 보안(Security) | 특정 메서드는 인증된 사용자만 접근 가능하게 할 때 |
💾 트랜잭션 처리 | 메서드 단위로 트랜잭션 시작/커밋/롤백 처리할 때(@Transactional) |
⚠️ 예외 처리 | 예외 발생 시 공통 로직 수행 (알림, 롤백 등) |
🧪 성능 측정 | 어떤 메서드가 시간이 오래 걸리는지 측정할 때 |
🧱 AOP 구성 요소
구성 요소 | 설명 |
@Aspect | 공통 기능(예: 로깅)을 모듈화한 클래스 |
JoinPoint | Advice가 적용될 수 있는 지점(예: 메서드 실행) |
Advice | 실제 수행될 작업 (@Before, @After, @Around) |
@Pointcut | Advice가 적용될 JoinPoint 의 조건 (어떤 메서드에 적용할지) |
Waeving | Advice 를 실제 코드에 적용하는 과정 |
📦 Spring에서의 AOP 사용 예시
package com.obo.system.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LoggingAspect {
@Before("execution(* com.obo.service..*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("************************");
System.out.println("메서드 실행 전 : "+joinPoint.getSignature().getName());
System.out.println("************************");
}
@AfterReturning(pointcut = "execution(* com.obo.service..*(..))", returning = "result")
public void logAfterSuccess(JoinPoint joinPoint, Object result) {
System.out.println("************************");
System.out.println("메서드 실행 후: " + joinPoint.getSignature().getName() + ", 반환값: " + result);
System.out.println("************************");
}
}
- @Aspect : AOP 기능이 담긴 클래스임을 나타낸다.
- @Before : 메서드 실행 전 로직을 수행한다.
- @AfterReturning : 메서드가 정상적으로 종료된 후 실행된다.(예외가 실행되지 않았을 경우)
- execution(...) : 어떤 메서드에 적용할지 지정하는 Pointcut 표현식
✅ Spring AOP 어노테이션 정리
어노테이션 | 설명 |
@Before | 메서드 실행전에 동작할 로직을 설정한다. |
@AfterReturning | 메서드가 정상적으로 종료된 후 실행된다. (예외가 발생하지 않았을 때) |
@AfterThrowing | 메서드 실행 중 예외가 발생했을 때 실행된다. |
@After | 메서드 실행 후, 정상 종료나 예외 상관없이 무조건 실행된다. |
@Around | 메서드 실행 전/후 전체를 감싸서 제어한다. (가장 강력) |
@Pointcut | 여러 Advice 에서 재사용 가능한 Pointcut 조건 정의용 |
Pointcut 표현식의 주요 패턴
패턴 | 설명 |
execution(* com.example.service.*.**(..) | 모든 메서드 |
execution(* com.example.service.*.get*(..)) | get 으로 시작하는 모든 메서드 |
execution(* com.example.service.*.*(String)) | String 파라미터를 받는 모든 메서드 |
execution(* com.example.service.*.*(@Param("id") String)) | 특정 파라미터 어노테이션을 가진 메서드 |
🔍 각각의 예제 코드
1. @Before
@Before("execution(* com.example.service..*(..))")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("Before: " + joinPoint.getSignature().getName());
}
2. @AfterReturning
@AfterReturning(pointcut = "execution(* com.example.service..*(..))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("After Returning: " + result);
}
3. @AfterThrowing
@AfterThrowing(pointcut = "execution(* com.example.service..*(..))", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
System.out.println("Exception thrown: " + ex.getMessage());
}
4. @After
@After(value = "execution(* com.obo.service..*(..))")
public void afterMethod(JoinPoint joinPoint) {
System.out.println("************************");
System.out.println("메서드 실행 후 (After) : " + joinPoint.getSignature().getName());
System.out.println("************************");
}
5. @Around
@Around(value = "execution(* com.obo.service..*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("************************");
System.out.println("Before Around");
Object result = joinPoint.proceed(); // 실제 메서드 실행
System.out.println("After Around");
System.out.println("************************");
return result;
}
@Around 는 메서드 전/후를 전체를 감싸서 제어하는데 JoinPoint 의 .proceed() 메서드 전 실행되고 난 후
모든 AOP 어노테이션이 설정된 메서드들이 실행되고 하위에 있는 메서드가 실행된다.
예를 들어 아래와 같은 AOP 가 존재한다고 해보자
package com.obo.system.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LoggingAspect {
@Before("execution(* com.obo.service..*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("************************");
System.out.println("메서드 실행 전 : "+joinPoint.getSignature().getName());
System.out.println("************************");
}
@AfterReturning(pointcut = "execution(* com.obo.service..*(..))", returning = "result")
public void logAfterSuccess(JoinPoint joinPoint, Object result) {
System.out.println("************************");
System.out.println("메서드 실행 후: " + joinPoint.getSignature().getName() + ", 반환값: " + result);
System.out.println("************************");
}
@After(value = "execution(* com.obo.service..*(..))")
public void afterMethod(JoinPoint joinPoint) {
System.out.println("************************");
System.out.println("메서드 실행 후 (After) : " + joinPoint.getSignature().getName());
System.out.println("************************");
}
@Around(value = "execution(* com.obo.service..*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("************************");
System.out.println("Before Around");
Object result = joinPoint.proceed(); // 실제 메서드 실행
System.out.println("After Around");
System.out.println("************************");
return result;
}
}
@Around 로 인해서 joinPoint.proceed() 시점에 @Before, @AfterReturning, @After AOP 어노테이션이 실행되고 난 후
하위 After Around 가 실행된다.
💡 참고사항
- @Around 는 성능 측정, 메서드 실행 자체를 제어할 때 많이 사용된다.
- @Before, @AfterReturning, @AfterThrowing 은 간단한 로직 삽입에 적합하다.
- @Pointcut 은 중복 제거 및 코드 관리에 유용하다.
반응형