다음에서는 AOP 대표적인 툴 중 @AspectJ(Annotation)을 이용하여 Aspect를 정의하고 테스트하는 방법에
대해서 다루고자 한다. @AspectJ(Annotation)은 AspectJ 5 버전에 추가된 Annotation이며, Spring 2.0
에서부터 이러한 Annotation에 대한 처리가 가능하므로, Spring 기반일 경우 별도의 Compiler나 Weaver 없이
@AspectJ(Annotation) 기반의 AOP 적용이 가능하다. 또한 Annotation을 이용하여 Aspect를 정의할 경우 별도
XML 파일에 대한 정의가 불필요하므로, Aspect 적용이 보다 간결해짐을 알 수 있을 것이다.
(단, Annotation은 JAVA 5 이상에서만 정의 가능함에 유의하도록 한다.)
@Advice 정의
다음에서는 Annotation을 이용하여 동작 시점별 Advice를 정의하는 방법에 대해 살펴보기로 한다.
Before Advice
@Before를 이용하여 Before Advice를 정의한다.
다음은
PrintStringUsingAnnotation
의
Before Advice 정의 부분이다. Before Advice인 beforeExecuteGetMethod()는 앞서 정의한
getMethods()라는 Pointcut 전에 "Before Advice of PrintStringUsingAnnotation"라는
문자열과 해당 Pointcut을 가진 클래스명, 메소드명을 출력하는 역할을 수행한다.
@Before("getMethods()")
public void beforeExecuteGetMethod(JoinPoint thisJoinPoint) {
Class targetClass = thisJoinPoint.getTarget().getClass();
Signature signature = thisJoinPoint.getSignature();
String opName = signature.getName();
System.out.println("Before Advice of PrintStringUsingAnnotation");
System.out.println("***" + targetClass + "." + opName + "()" + "***");
}
beforeExecuteGetMethod()는 1개의 입력 인자(JoinPoint)를 가지고 있는데
Target 클래스명, 메소드명 등과 같은 Target 정보를 포함하고 있다. Target 정보가 불필요한 Advice인 경우에는
JoinPoint라는 입력 인자를 선언하지 않아도 된다.
AfterReturning Advice
@AfterReturning을 이용하여 AfterReturning Advice를 정의한다.
다음은
PrintStringUsingAnnotation
의
AfterReturning Advice 정의 부분으로 해당 Pointcut 실행 결과를 retVal이라는 변수에 담도록 정의하고 있다.
AfterReturning Advice인 afterReturningExecuteGetMethod()는 앞서 정의한 Pointcut 후에 ,
"AfterReturning Advice of PrintStringUsingAnnotation"라는 문자열과 해당 Pointcut을 가진
클래스명, 메소드명을 출력하는 역할을 수행한다.
@AfterReturning(pointcut = "getMethods()", returning = "retVal")
public void afterReturningExecuteGetMethod(JoinPoint thisJoinPoint,
Object retVal) {
Class targetClass = thisJoinPoint.getTarget().getClass();
Signature signature = thisJoinPoint.getSignature();
String opName = signature.getName();
System.out.println("AfterReturning Advice of PrintStringUsingAnnotation");
System.out.println("***" + targetClass + "." + opName + "()" + "***");
}
afterReturningExecuteGetMethod()는 2개의 입력 인자(JoinPoint, Object)를 가지고 있는데
첫번째 인자는 Target 클래스명, 메소드명 등과 같은 Target 정보를 포함하고 있으며, 두번째 인자는 해당 Pointcut의
실행 결과이다. AfterReturning Advice에서 특정 Pointcut 실행 결과를 참조해야 한다면, Advice 정의시 returning의
값을 정의하고 해당하는 메소드의 입력 인자명을 동일하게 정의해주도록 한다. 각 입력 인자는 AfterReturning Advice 정의시
필요에 따라 선택 정의할 수 있다.
AfterThrowing Advice
@AfterThrowing을 이용하여 AfterThrowing Advice를 정의한다.
다음은
PrintStringUsingAnnotation
의
AfterThrowing Advice 정의 부분으로 해당 Pointcut 실행시 발생한 Exception 객체를 exception이라는 변수에 담도록 정의하고 있다.
AfterThrowing Advice인 afterThrowingExecuteGetMethod()는 앞서 정의한 Pointcut에서 Exception이 발생한 후에 ,
"AfterThrowing Advice of PrintStringUsingAnnotation"라는 문자열과 해당 Pointcut을 가진
클래스명, 메소드명을 출력하는 역할을 수행한다.
@AfterThrowing(pointcut = "getMethods()", throwing = "exception")
public void afterThrowingExecuteGetMethod(JoinPoint thisJoinPoint,
Exception exception) {
Class targetClass = thisJoinPoint.getTarget().getClass();
Signature signature = thisJoinPoint.getSignature();
String opName = signature.getName();
System.out.println("AfterThrowing Advice of PrintStringUsingAnnotation");
System.out.println("***" + targetClass + "." + opName + "()" + "***");
}
afterThrowingExecuteGetMethod()는 2개의 입력 인자(JoinPoint, Exception)를 가지고 있는데
첫번째 인자는 Target 클래스명, 메소드명 등과 같은 Target 정보를 포함하고 있으며, 두번째 인자는
Pointcut 실행시 발생한 Exception 객체이다. AfterThrowing Advice에서 특정 Pointcut 실행시 발생한 Exception을
참조해야 한다면, Advice 정의시 throwing의 값을 정의하고 해당하는 메소드의 입력 인자명을 동일하게 정의해주도록 한다.
각 입력 인자는 AfterThrowing Advice 정의시 필요에 따라 선택 정의할 수 있다.
After(finally) Advice
@After를 이용하여 After(finally) Advice를 정의한다.
다음은
PrintStringUsingAnnotation
의
After(finally) Advice 정의 부분이다. After(finally) Advice인 afterExecuteGetMethod()는 앞서 정의한
getMethods()라는 Pointcut 후에 "After(finally) Advice of PrintStringUsingAnnotation"라는
문자열과 해당 Pointcut을 가진 클래스명, 메소드명을 출력하는 역할을 수행한다.
@After("getMethods()")
public void afterExecuteGetMethod(JoinPoint thisJoinPoint) {
Class targetClass = thisJoinPoint.getTarget().getClass();
Signature signature = thisJoinPoint.getSignature();
String opName = signature.getName();
System.out
.println("After(finally) Advice of PrintStringUsingAnnotation");
System.out.println("***" + targetClass + "." + opName + "()" + "***");
}
afterExecuteGetMethod()는 1개의 입력 인자(JoinPoint)를 가지고 있는데
Target 클래스명, 메소드명 등과 같은 Target 정보를 포함하고 있다. Target 정보가 불필요한 Advice인 경우에는
JoinPoint라는 입력 인자를 선언하지 않아도 된다.
Around Advice
@Around를 이용하여 Around Advice를 정의한다.
다음은
PrintStringAroundUsingAnnotation
의
Around Advice 정의 부분이다. Around Advice인 aroundExecuteUpdateMethod()는 updateMethods()라는
Pointcut 후에 "Around Advice of PrintStringUsingAnnotation"라는 문자열과 해당 Pointcut을 가진
클래스명, 메소드명을 출력하는 역할을 수행한다.
@Around("updateMethods()")
public Object aroundExecuteUpdateMethod(ProceedingJoinPoint thisJoinPoint)
throws Throwable {
Class targetClass = thisJoinPoint.getTarget().getClass();
Signature signature = thisJoinPoint.getSignature();
String opName = signature.getName();
System.out.println("Around Advice of PrintStringUsingAnnotation");
System.out.println("***" + targetClass + "." + opName + "()" + "***");
// before logic
Object retVal = thisJoinPoint.proceed();
// after logic
return retVal;
}
aroundExecuteUpdateMethod()는 1개의 입력 인자(ProceedingJoinPoint)를 가지고 있는데
proceed()라는 메소드 호출을 통해 대상 Pointcut을 실행할 수 있으며, Target 클래스명, 메소드명 등과 같은 Target
정보도 포함하고 있다. 즉, Pointcut 전, 후 처리가 가능하며, Pointcut 실행 시점을 결정할 수 있다.
또한 다른 Advice와는 달리 입력값, target, return 값 등에 대해 변경이 가능하다.
Target 정보가 불필요한 Advice인 경우에는 ProceedingJoinPoint라는 입력 인자를 선언하지 않아도 된다.
Aspect 실행
이제 테스트코드
PrintStringAspectUsingAnnotationTest
를
이용하여 앞서 언급한 Aspect들이 정상적으로 동작하는지 확인해 보도록 하자.
다음은 테스트코드
PrintStringAspectUsingAnnotationTest
의
testGetUser() 메소드로 integration.anyframe.services 패키지에 속한 UserServiceImpl 클래스의 getXXX() 메소드를 호출하는 로직으로
구성되어 있다.
public void testGetUser() throws Exception {
UserService userService = (UserService) context.getBean(UserService.ROLE);
String userID = "woos41";
// 1. get user
userService.getUser(userID);
// 2. get user with exception
try {
userService.getUserWithException();
} catch (EmpException e) {
// ignore
}
}
첫번째 로직 userService.getUser(userID); 실행시
Before, AfterReturning, After Advice가 적용되며,
콘솔창에 다음과 같은 실행 결과를 포함하게 된다.
Before Advice of PrintStringUsingAnnotation
***class integration.anyframe.services.aop.service.UserServiceImpl.getUser()***
AfterReturning Advice of PrintStringUsingAnnotation
***class integration.anyframe.services.aop.service.UserServiceImpl.getUser()***
After(finally) Advice of PrintStringUsingAnnotation
***class integration.anyframe.services.aop.service.UserServiceImpl.getUser()***
또한 두번째 로직 userService.getUserWithException(); 실행시 테스트를 위해 getUserWithException() 메소드 내에서
EmpException을 throw 하고 있으므로,
Before, AfterThrowing, After Advice가 적용되어 콘솔창에 다음과 같은 실행
결과를 포함하게 된다.
Before Advice of PrintStringUsingAnnotation
***class integration.anyframe.services.aop.service.UserServiceImpl.getUserWithException()***
...중략...
AfterThrowing Advice of PrintStringUsingAnnotation
***class integration.anyframe.services.aop.service.UserServiceImpl.getUserWithException()***
After(finally) Advice of PrintStringUsingAnnotation
***class integration.anyframe.services.aop.service.UserServiceImpl.getUserWithException()***
다음은 테스트코드
PrintStringAspectUsingAnnotationTest
의
testUpdateUser() 메소드로 integration.anyframe.services 패키지에 속한 UserServiceImpl 클래스의 updateXXX() 메소드를 호출하는 로직으로
구성되어 있다.
public void testUpdateUser() throws EmpException {
UserService userService = (UserService) context.getBean(UserService.ROLE);
UserVO userVO = new UserVO();
userVO.setUserId("woos41");
userVO.setUserName("updgang");
userVO.setCellPhone("9876543210");
userVO.setAddr("kamala road --");
// 1. update user
userService.updateUser(userVO);
}
userService.updateUser(userID); 실행시
Around Advice가 적용되며, 콘솔창에 다음과 같은 실행 결과를 포함하게 된다.
Around Advice of PrintStringUsingAnnotation
***class integration.anyframe.services.aop.service.UserServiceImpl.updateUser()***