Spring ‐ 스프링 AOP 구현 - dnwls16071/Backend_Study_TIL GitHub Wiki

📚 스프링 AOP 구현

@Slf4j
@Aspect
public class AspectV1 {

        // 포인트컷 표현식을 직접 기재
	@Around("execution(* com.jwj.aop..*(..))")
	public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
		log.info("[log] {}", joinPoint.getSignature());
		return joinPoint.proceed();
	}
}
@Slf4j
@Aspect
public class AspectV2 {

        // @Pointcut 어노테이션을 사용해서 포인트컷 표현식을 별도로 분리
	@Pointcut("execution(* com.jwj.aop.order..*(..))")
	private void allOrder() {}

        // 분리
	@Around("allOrder()")
	public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
		log.info("[log] {}", joinPoint.getSignature());
		return joinPoint.proceed();
	}
}
@Slf4j
@Aspect
public class AspectV3 {

        // 패키지 경로를 Pointcut으로 분리
	@Pointcut("execution(* com.jwj.aop.order..*(..))")
	private void allOrder() {}

        // 클래스 이름 패턴이 Service로 끝난다.
	@Pointcut("execution(* *..*Service.*(..))")
	private void allService() {}

	@Around("allOrder()")
	public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
		log.info("[log] {}", joinPoint.getSignature());
		return joinPoint.proceed();
	}

        // 위에서 개발한 2개의 Pointcut을 조합
	@Around("allOrder() && allService()")
	public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
		try {
			log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
			Object result = joinPoint.proceed();
			log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
			return result;
		} catch (Exception e) {
			log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
			throw e;
		} finally {
			log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
		}
	}
}
// 포인트컷을 공용으로 사용하기 위해 별도의 외부 클래스에 모아둔다.
public class Pointcuts {

	@Pointcut("execution(* com.jwj.aop.order..*(..))")
	public void allOrder(){}

	@Pointcut("execution(* *..*Service.*(..))")
	public void allService(){}
	
	@Pointcut("allOrder() && allService()")
	public void orderAndService() {}
}
@Slf4j
@Aspect
public class AspectV4 {

	@Around("com.jwj.aop.order.Pointcuts.allOrder()") // 패키지 전체 경로 명시
	public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
		log.info("[log] {}", joinPoint.getSignature());
		return joinPoint.proceed();
	}

	@Around("com.jwj.aop.order.Pointcuts.orderAndService()")
	public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
		try {
			log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
			Object result = joinPoint.proceed();
			log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
			return result;
		} catch (Exception e) {
			log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
			throw e;
		} finally {
			log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
		}
	}
}
@Slf4j
public class AspectV5 {

	@Aspect
	@Order(2) // 실행 순서를 지정
	public static class LogAspect {

		@Around("com.jwj.aop.order.Pointcuts.allOrder()")
		public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
			log.info("[log] {}", joinPoint.getSignature());
			return joinPoint.proceed();
		}
	}

	@Aspect
	@Order(1)
	public static class TxAspect {

		@Around("com.jwj.aop.order.Pointcuts.orderAndService()")
		public Object doTransaction(ProceedingJoinPoint joinPoint) throws
				Throwable {
			try {
				log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
				Object result = joinPoint.proceed();
				log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
				return result;
			} catch (Exception e) {
				log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
				throw e;
			} finally {
				log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
			}
		}
	}
}

📚 어드바이스 종류(시점 중요)

  • @Around : 메서드 호출 전후 수행
  • @Before : 조인 포인트 실행 이전에 수행
  • @AfterReturning : 조인 포인트가 정상 완료된 후 실행
  • @AfterThrowing : 메서드가 예외를 던지는 경우 실행
  • @After : 조인 포인트가 정상 또는 예외와 관계없이 실행
@Slf4j
@Aspect
public class AspectV6 {

	@Around("com.jwj.aop.order.Pointcuts.orderAndService()")
	public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable
	{
		try {
			//@Before : 조인 포인트 실행 이전
			log.info("[around][트랜잭션 시작] {}", joinPoint.getSignature());
			Object result = joinPoint.proceed();
			//@AfterReturning : 조인 포인트 정상 완료된 후 실행
			log.info("[around][트랜잭션 커밋] {}", joinPoint.getSignature());
			return result;
		} catch (Exception e) {
			//@AfterThrowing : 메서드가 예외를 던진 경우 실행
			log.info("[around][트랜잭션 롤백] {}", joinPoint.getSignature());
			throw e;
		} finally {
			//@After : 정상 또는 예외와 관계없이 실행
			log.info("[around][리소스 릴리즈] {}", joinPoint.getSignature());
		}
	}

	@Before("com.jwj.aop.order.Pointcuts.orderAndService()")
	public void doBefore(JoinPoint joinPoint) {
		log.info("[before] {}", joinPoint.getSignature());
	}

	@AfterReturning(value = "com.jwj.aop.order.Pointcuts.orderAndService()", returning = "result")
	public void doReturn(JoinPoint joinPoint, Object result) {
		log.info("[return] {} return={}", joinPoint.getSignature(), result);
	}

	@AfterThrowing(value = "com.jwj.aop.order.Pointcuts.orderAndService()", throwing = "ex")
	public void doThrowing(JoinPoint joinPoint, Exception ex) {
		log.info("[ex] {} message={}", joinPoint.getSignature(), ex.getMessage());
	}

	@After("com.jwj.aop.order.Pointcuts.orderAndService()")
	public void doAfter(JoinPoint joinPoint) {
		log.info("[after] {}", joinPoint.getSignature());
	}
}

@Around 어노테이션 하나만 있어도 되지만 범위를 줄여가며 제약을 두는 명확한 코드가 더 좋기에 각 상황에 맞는 어노테이션을 활용하는 것을 추천한다.