
1. Aspect
Aspect는 횡단 관심사를 모듈화한 클래스입니다.
로깅, 트랜잭션 관리, 보안 검증과 같은 공통된 기능을 별도의 모듈로 분리하여 핵심 비즈니스 로직에 영향을 주지 않도록 설계합니다.
특징:
- 여러 클래스나 메서드에 공통적으로 적용될 기능을 한 곳에 모아 재사용성을 높입니다.
- Spring AOP에서는 @Aspect 어노테이션을 사용해 Aspect 클래스를 정의합니다.
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.aop.domain..*(..))")
public void logBefore() {
log.info("메서드 실행 전 로그");
}
}
2. Advice
@Before("execution(* com.aop.domain..*(..))")
public void logBefore() {
log.info("메서드 실행 전 로그");
}3. JoinPoint
JoinPoint는 AOP가 적용될 수 있는 지점을 의미합니다.
Spring AOP에서는 메서드 호출, 객체 생성 등이 JoinPoint에 해당합니다.
특징:
JoinPoint를 활용하면 메서드 실행 시점에서 메서드 이름, 파라미터, 반환 값 등의 정보를 가져올 수 있습니다.
Spring AOP는 메서드 호출에만 JoinPoint를 지원합니다.
@Before("execution(* com.aop.domain..*(..))")
public void logMethodDetails(JoinPoint joinPoint) {
log.info("실행된 메서드 이름: {}", joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
log.info("전달된 파라미터 [{}]: {}", i, args[i]);
}
}
}
joinPoint.getSignature().getName()으로 호출된 메서드의 이름을 가져옵니다.
joinPoint.getArgs()를 통해 전달된 파라미터를 확인할 수 있습니다.
4. Pointcut
Pointcut은 Advice를 적용할 JoinPoint를 정의하는 표현식입니다.
특정 패키지, 클래스, 메서드에만 Advice를 적용하거나 제외할 수 있습니다.
Pointcut 표현식:
execution: 메서드 실행을 기준으로 Pointcut 정의.
- 예: execution(* com.example.service..*(..)) → com.example.service 패키지의 모든 클래스의 모든 메서드.
@Before("execution(* com.aop.domain..api..*(..))")
public void logBeforeExecution() {
log.info("[execution] 메서드 실행 전 로그");
}
리턴 타입 지정 : * 모든 리턴 타입을 의미합니다.
패키지 지정 : com.aop.domain 메서드가 속한 클래스가 이 패키지 또는 그 하위 패키지에 있어야 한다는 조건을 지정합니다.
하위 패키지 포함 : ..는 0개 이상의 하위 패키지를 의미합니다.
클래스 이름의 와일드카드 : 클래스 이름이 무엇이든 상관없이 모든 클래스의 메서드를 대상으로 합니다.
메서드 이름과 파라미터 지정 :
*는 메서드 이름을 지정하는 부분입니다. 여기서 *는 모든 이름의 메서드를 의미합니다.(..) 는 메서드의 파라미터 지정과 0개 이상의 모든 파라미터를 허용합니다.within: 특정 클래스 또는 패키지 내의 모든 메서드에 적용.
예: within(com.example.service..*) → com.example.service 패키지와 하위 패키지의 모든 메서드.
@Before("within(com.aop.domain..*)")
public void logBeforeWithin() {
log.info("[within] 메서드 실행 전 로그");
}
@annotation: 특정 어노테이션이 붙은 메서드에만 적용.
예: @annotation(com.example.annotation.Loggable).
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {
}
@Before("@annotation(com.aop.common.annotation.Loggable)")
public void logBeforeAnnotation() {
log.info("[annotation] 메서드 실행 전 로그");
}
package com.example.service;
import com.example.annotation.Loggable;
public class UserService {
@Loggable
public void getUser() {
// 이 메서드에만 logAnnotatedMethod 적용
}
}
| 표현식 | 적용 대상 | 주요 사용 사례 |
|---|---|---|
| execution | 특정 메서드 실행 시점을 기준으로 AOP 적용 | 특정 패키지/메서드의 실행 시점에 대한 공통 로직 적용. |
| within | 특정 클래스/패키지 내의 모든 메서드에 AOP 적용 | 특정 영역 내 모든 메서드에 대해 포괄적인 동작 정의. |
| @annotation | 특정 어노테이션이 붙은 메서드에만 AOP 적용 | 메서드에 맞춤형 동작(예: 로깅, 검증, 보안 등) 적용. |
@Slf4j
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.aop.domain..api..*(..))")
public void logBeforeExecution() {
log.info("[execution] 메서드 실행 전 로그");
}
@Before("within(com.aop.domain..*)")
public void logBeforeWithin() {
log.info("[within] 메서드 실행 전 로그");
}
@Before("@annotation(com.aop.common.annotation.Loggable)")
public void logBeforeAnnotation() {
log.info("[annotation] 메서드 실행 전 로그");
}
@Before("execution(* com.aop.domain..*(..))")
public void logMethodDetails(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
log.info("전달된 파라미터 [{}]: {}", i, args[i]);
}
}
log.info("실행된 메서드 이름: {}", joinPoint.getSignature().getName());
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
private final CategoryRepository categoryRepository;
private final ProductQueryRepository productQueryRepository;
@Loggable
public List<ProductResponse> getAll() {
...
}
}
[execution] 메서드 실행 전 로그
[within] 메서드 실행 전 로그
실행된 메서드 이름: findAll
[annotation] 메서드 실행 전 로그
[within] 메서드 실행 전 로그
실행된 메서드 이름: getAll
@AfterThrowing 어노테이션은 특정 메서드에서 예외가 발생했을 때 동작하는 Advice를 정의할 수 있습니다.작동 원리
throwing 속성에 지정된 변수로 예외 정보를 전달받아 로그를 출력하거나 알림 시스템과 연동합니다.@Slf4j
@Aspect
@Component
public class ExceptionLoggingAspect {
@Pointcut("execution(* com.aop.domain..service..*(..))")
public void serviceMethods() {
}
@AfterThrowing(pointcut = "serviceMethods()", throwing = "exception")
public void logException(ServiceException exception) {
log.error("AfterThrowing : [{}] {}", exception.getCode(), exception.getMessage());
}
}
within(com.aop.domain..*)는 com.aop.domain 패키지의 모든 클래스에 포함된 메서드를 대상으로 설정.throwing 속성:throwing = "exception"을 통해 발생한 예외 객체를 Advice로 전달받습니다.log.error로 예외 메시지와 스택 트레이스를 기록하여 디버깅 및 문제 분석에 활용.결과 로그 예시
AfterThrowing : [NOT_FOUND_USER] User를 찾을 수 없습니다.
@Around 어노테이션은 메서드 실행 전후에 특정 로직을 실행할 수 있는 Advice를 정의합니다.작동 원리
@Around는 Pointcut에 정의된 메서드 호출을 가로채고, 실행 전후의 시점을 제어합니다.ProceedingJoinPoint를 사용하여 실제 메서드를 호출하고, 실행 결과를 반환받습니다.@Slf4j
@Aspect
@Component
public class ExecutionTimeAspect {
@Pointcut("execution(* com.aop.domain..service..*(..))")
public void serviceMethods() {
}
@Around("serviceMethods()")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info("{} 메서드 실행 시간: {} ms", joinPoint.getSignature(), (endTime - startTime));
return result;
}
}
ProceedingJoinPoint:proceed()를 호출하여 실제 메서드를 실행하고 결과를 반환.ProductService.getById())과 실행 시간(endTime - startTime)을 로그로 출력.결과로그 예시
ProductResponse com.aop.domain.product.service.ProductService.getById(Long) 메서드 실행 시간: 32 ms