AOP - 2편 (Advice 정의)
[자바 기반 설정 방식에서의 Advice 정의]
앞서 스프링 AOP에서 활용 가능한 Advice로 다섯 가지 종류가 있다고 했다. 이제 각각의 구현 방식을 예를 들어 살펴보자.
Before
이미 앞서 예를 들었지만 Advice 기능을 하는 메서드에 @Before 애너테이션(org.aspectj.lang.annotation.Before)을 붙인 다음, Pointcut 표현식을 추가하면 된다. 이때 사용되는 Pointcut 표현식에 대해서는 AOP - 3편에서 자세히 다루겠다.
@Before 애너테이션이 붙은 메서드는 JoinPoint를 매개변수로 선언하고 있는데, 메서드가 호출될 때 전달되는 인수를 통해 실행 중인 메서드의 정보를 구할 수 있다.
- @Before 애너테이션을 활용한 Advice 정의
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MethodStartLoggingAspect {
@Before("execution(* *..*ServiceImpl.*(..))")
public void startLog(JoinPoint jp) {
System.out.println("메서드 시작: " + jp.getSignature());
}
}
After Returning
After Returning Advice 작성 방법은 Before Advice와 거의 같다. 실행되는 시점은 대상 메서드가 정상적으로 종료한 후다.
- @AfterReturning 애너테이션을 활용한 Advice 정의
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MethodNormalEndLoggingAspect {
@AfterReturning("execution(* *..*ServiceImpl.*(..))")
public void startLog(JoinPoint jp) {
System.out.println("메서드 정상 종료: " + jp.getSignature());
}
}
After Returning Advice를 사용할 때는 정상적으로 종료한 메서드의 반환값을 구할 수 있다. @AfterReturning 애너테이션의 returning 속성에 반환값을 받을 매개변수의 이름을 지정하면 된다.
- @AfterReturning 애너테이션을 활용한 Advice 정의(반환값 정보 받기)
@Aspect
@Component
public class MethodNormalEndLoggingAspect {
@AfterReturning(value = "execution(* *..*ServiceImpl.*(..))", returning = "user")
public void startLog(JoinPoint jp, User user) {
System.out.println("메서드 정상 종료: " + jp.getSignature() + "반환값=" + user);
}
}
After Throwing
After Throwing Advice는 After Returning Advice와 반대로 예외가 발생해서 비정상적으로 종료될 때 실행된다. 또한 이때 발생한 예외가 무엇인지 알고 싶다면 @AfterThrowing 애너테이션의 throwing 속성에 예외를 받을 매개변수의 이름을 지정하면 된다.
- @AfterThrowing 애너테이션을 활용한 Advice 정의
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MethodExceptionEndLoggingAspect {
@AfterThrowing(value = "execution(* *..*ServiceImpl.*(..))", throwing = "e")
public void endLog(JoinPoint jp, RuntimeException e) {
System.out.println("메서드 비정상 종료: " + jp.getSignature());
e.printStackTrace();
}
}
After Throwing Advice에서는 예외를 다시 던져 전파(propagation)하는 것이 가능하다. 다음과 같이 특정 예외가 발생하면 일률적으로 다른 예외 형태로 변환해줘야 할 때 상당히 유용하다.
- @AfterThrowing 애너테이션을 활용한 Advice 정의(예외 변환)
@Aspect
@Component
public class MethodExceptionPropagationAspect {
@AfterThrowing(value = "execution(* *..*ServiceImpl.*(..))", throwing = "e")
public void endLog(JoinPoint jp, DataAccessException e) {
throw new ApplicationException(e);
}
}
참고로 After Throwing Advice는 예외가 외부로 던져지는 것을 막지는 못하기 때문에 예외가 발생했을 때 꼭 필요한 동작을 수행하게 만든 다음, 예외는 Advice가 없을 때처럼 외부로 던지도록 만들어야 한다. 만약 발생한 예외가 밖으로 던져지는 것을 굳이 막아야 하는 상황이라면 AfterThrowing 대신 Around Advice를 사용하면 된다.
After
After Advice는 After Returning Advice나 After Throwing Advice와 달리 메서드가 정상 종료 여부나 예외 발생 여부와 상관없이 무조건 실행된다. 마치 try-catch 구문의 finally와 같은 역할을 한다.
- @After 애너테이션을 활용한 Advice 정의
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MethodEndLoggingAspect {
@After("execution(* *..*ServiceImpl.*(..))")
public void endLog(JoinPoint jp) {
System.out.println("메서드 종료: " + jp.getSignature());
}
}
Around
Around Advice는 가장 강력한 Advice이며, 메서드의 실행 전과 후의 처리는 물론, Pointcut이 적용된 대상 메서드 자체도 실행할 수 있다.
- @Around 애너테이션을 활용한 Advice 정의
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MethodLoggingAspect {
@Around("execution(* *..*ServiceImpl.*(..))")
public Object log(ProceedingJoinPoint jp) throws Throwable {
System.out.println("메서드 시작: " + jp.getSignature());
try {
// 대상 메서드 실행
Object result = jp.proceed();
System.out.println("메서드 정상 종료: " + jp.getSignature() + " 반환값=" + result);
return result;
} catch(Exception e) {
System.out.println("메서드 비정상 종료: " + jp.getSignature());
e.printStackTrace();
throw e;
}
}
}
[XML 기반 설정 방식에서의 Advice 정의]
지금까지는 Advice를 정의할 때 자바 기반 설정 방식을 사용했다. 다음은 XML 기반 설정 방식으로 Advice를 정의하는 방법을 알아보자. 우선 애너테이션을 사용하지 않은 Aspect 클래스를 만든 다음, XML 파일에서 AOP를 설정하면 된다.
- Aspect 구현
import org.aspectj.lang.Joinpoint;
public class MethodStartLoggingAspect {
public void startLog(JoinPoint jp) {
System.out.println("메서드 시작: " + jp.getSignature());
}
}
이렇게 Aspect가 만들어졌다면 이번엔 MethodStartLoggingAspect 클래스의 startLog 메서드를 Before Advice로 정의해보자.
- Before Advice 설정(XML 기반 설정 방식)
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 생략 -->
<aop:config>
<aop:aspect ref="loggingAspect">
<aop:before pointcut="execution(* *..*ServiceImpl.*(..))" method="startLog" />
</aop:aspect>
</aop:config>
<bean id="loggingAspect" class="com.example.aspect.MethodStartLoggingAspect" />
</beans>
- <aop:config> 요소 내에 여러 개의 Aspect를 정의할 수 있다.
- Aspect를 정의할 때는 <aop:aspect>를 사용하고 Aspect의 빈 ID를 ref 속성에 지정한다. 이 과정은 자바 기반 설정 방식에서 @Aspect 애너테이션을 사용하는 것과 같은 역할을 한다.
- <aop:before> 요소에서 Before Advice를 정의한다. Pointcut 속성에 Pointcut 표현식을 설정하고 method 속성에 적용할 대상 메서드의 이름을 기술한다.
Before Advice 외의 After Returning, After Throwing, After, Around Advice도 모두 같은 방법으로 정의할 수 있다.
댓글