Contents
see ListSpring AOP로 로깅과 트랜잭션 처리
AOP(Aspect-Oriented Programming)는 횡단 관심사(로깅, 트랜잭션, 보안 등)를 비즈니스 로직과 분리하는 프로그래밍 패러다임입니다. Spring AOP로 코드 중복 없이 공통 기능을 적용할 수 있습니다.
언제 사용하나요?
- 메서드 실행 시간 측정
- 입출력 파라미터 로깅
- 트랜잭션 관리
- 권한 검사
- 예외 처리 통합
AOP 용어
Aspect: 횡단 관심사 모듈 (로깅, 트랜잭션 등)
Join Point: Aspect 적용 가능 지점 (메서드 실행 등)
Pointcut: Join Point 선택 표현식
Advice: 실제 실행되는 코드 (Before, After, Around 등)
Target: Aspect가 적용되는 대상 객체의존성 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>로깅 Aspect 예시
@Aspect
@Component
@Slf4j
public class LoggingAspect {
// 모든 Service 클래스의 모든 메서드
@Pointcut("execution(* com.example..service.*.*(..))")
public void serviceLayer() {}
// 메서드 실행 전
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
log.info("메서드 시작: {}.{}",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName());
log.info("파라미터: {}", Arrays.toString(joinPoint.getArgs()));
}
// 메서드 정상 종료 후
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
log.info("메서드 완료: {}, 반환값: {}",
joinPoint.getSignature().getName(), result);
}
// 예외 발생 시
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
log.error("메서드 예외: {}, 예외: {}",
joinPoint.getSignature().getName(), ex.getMessage());
}
}실행 시간 측정
@Aspect
@Component
@Slf4j
public class PerformanceAspect {
@Around("execution(* com.example..controller.*.*(..))")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint)
throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
return result;
} finally {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
log.info("{}.{} 실행시간: {}ms",
joinPoint.getSignature().getDeclaringType().getSimpleName(),
joinPoint.getSignature().getName(),
executionTime);
if (executionTime > 1000) {
log.warn("느린 메서드 감지: {}ms", executionTime);
}
}
}
}커스텀 어노테이션 사용
// 커스텀 어노테이션 정의
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
String value() default "";
}
// Aspect에서 어노테이션 감지
@Aspect
@Component
public class CustomAnnotationAspect {
@Around("@annotation(loggable)")
public Object logAnnotatedMethod(ProceedingJoinPoint joinPoint,
Loggable loggable) throws Throwable {
log.info("@Loggable 메서드 시작: {} - {}",
joinPoint.getSignature().getName(),
loggable.value());
return joinPoint.proceed();
}
}
// 사용
@Service
public class UserService {
@Loggable("사용자 조회")
public User findById(Long id) {
return userRepository.findById(id);
}
}트랜잭션 AOP
@Aspect
@Component
public class TransactionAspect {
@Autowired
private PlatformTransactionManager transactionManager;
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object manageTransaction(ProceedingJoinPoint joinPoint)
throws Throwable {
TransactionStatus status = transactionManager.getTransaction(
new DefaultTransactionDefinition());
try {
Object result = joinPoint.proceed();
transactionManager.commit(status);
return result;
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
// 실제로는 @Transactional 사용
@Service
public class OrderService {
@Transactional
public void createOrder(OrderDto dto) {
orderRepository.save(dto.toEntity());
inventoryService.decreaseStock(dto.getProductId());
paymentService.processPayment(dto.getPaymentInfo());
}
}Pointcut 표현식
// 패키지 내 모든 메서드
execution(* com.example.service.*.*(..))
// 특정 클래스 모든 메서드
execution(* com.example.service.UserService.*(..))
// 특정 리턴 타입
execution(String com.example..*.*(..))
// 특정 파라미터
execution(* com.example..*.*(Long, String))
// 어노테이션
@annotation(org.springframework.transaction.annotation.Transactional)
@within(org.springframework.stereotype.Service)
// 조합
@Pointcut("execution(* com.example..*.*(..)) && !execution(* com.example..*.get*(..))")주의사항
- 같은 클래스 내부 호출은 AOP 적용 안됨 (프록시 한계)
- private 메서드는 AOP 적용 불가
- @Async와 함께 사용 시 순서 주의
- AOP 남용 시 디버깅 어려움