AOP Example - Exception Transfer
특정 비즈니스 로직 수행시 발생할 수 있는 Exception에 대한 로그 및 메시지 처리를 수행하기 위해 핵심 비즈니스 로직외에
Exception 처리 로직이 추가되어야 한다. 때문에 핵심 비즈니스 로직외에 매 로직마다 반복되는 try ~ catch 블럭으로 인해
코드가 복잡해진다. 만일 별도 Aspect을 통해 공통적으로 Exception들을 처리하게 하고, 각 비즈니스 로직에서 try ~ catch 블럭을
제거할 수 있다면 코드가 훨씬 간단해지고, 궁극적으로 개발자는 비즈니스 로직에만 집중할 수 있는 기반이 마련될 수 있을 것이다.
다음에서는 AOP 대표적인 툴 중 Spring AOP를 이용하여 XML 스키마 기반에서 ExceptionTransfer를 위한 Aspect를 생성하고
테스트해 보도록 할 것이다.
ExceptionTransfer Aspect 적용 대상은
UserService
이며,
특정 메소드(get*) 실행시 Exception이 발생한 경우 이를 처리하기 위한 역할을 수행하게 될 것이다.
Aspect 정의
Spring 속성 정의 XML(
applicationContext-aop-exception.xml
)
파일 내에 Aspect 클래스를 Bean으로 정의한 후, 해당 Aspect에 대한 Pointcut과 Advice를 정의한다.
<bean id="exceptionTransfer"
class="integration.anyframe.services.aop.aspect.ExceptionTransfer" />
<aop:config>
<aop:aspect ref="exceptionTransfer">
<aop:pointcut id="getMethods"
expression="execution(public * integration.anyframe.services..*Impl.get*(..))" />
<aop:after-throwing method="transfer"
throwing="exception" pointcut-ref="getMethods" />
</aop:aspect>
</aop:config>
ExceptionTransfer는 integration.anyframe.services 패키지 내에 속한 모든 클래스 중 클래스명이 Impl로 끝나는
클래스의 메소드 중 메소드명이 get으로 시작하는 메소드 실행시 발생한 Exception에 대해 처리하는 역할을 수행한다.
Advice 구현
다음과 같이
ExceptionTransfer
라는
Aspect 클래스를 생성한다.
public class ExceptionTransfer implements ApplicationContextAware {
private MessageSource messageSource;
public void setApplicationContext(ApplicationContext applicationContext) {
this.messageSource = (MessageSource) applicationContext
.getBean("messageSource");
}
public void transfer(JoinPoint thisJoinPoint, Exception exception)
throws EmpException {
String pkgName = thisJoinPoint.getTarget().getClass().getName()
.toLowerCase();
int lastIndex = pkgName.lastIndexOf(".");
String className = pkgName.substring(lastIndex + 1);
String opName = (thisJoinPoint.getSignature().getName()).toLowerCase();
Log logger = LogFactory.getLog(thisJoinPoint.getTarget().getClass());
if (exception instanceof EmpException) {
EmpException empEx = (EmpException) exception;
logger.error(empEx.getMessage(), empEx);
throw empEx;
}
if (exception instanceof QueryServiceException) {
logger.error(messageSource.getMessage("error." + className + "."
+ opName + ".query", new String[] {},
Locale.getDefault()), exception);
throw new EmpException(messageSource.getMessage("error."
+ className + "." + opName + ".query", new String[] {},
Locale.getDefault()), exception);
} else {
logger.error(messageSource.getMessage("error." + className + "."
+ opName, new String[] {}, Locale.getDefault()),
exception);
throw new EmpException(messageSource.getMessage("error."
+ className + "." + opName, new String[] {}, Locale
.getDefault()), exception);
}
}
}
transfer() 메소드에서는 발생한 Exception 객체의 유형을 EmpException, QueryServiceException, 기타로 구분하고
Exception이 발생한 Target 클래스와 메소드명을 조합한 message key를 이용하여 해당하는 메시지를 얻어낸다.
그런 후에 이 메시지를 이용하여 ERROR 레벨의 로그를 남긴 후에 EmpException으로 전환하여 throw한다.
Aspect 실행
UserService를 호출하여, 등록되지 않은 UserID를 이용하여 User 정보를 조회함으로써 Exception 발생시키는 로직으로
구성된
ExceptionTransferAspectTest
클래스를 실행시키면 ExceptionTransfer Aspect가 적용되어, 콘솔창을 통해 다음과 같은 형태의 로그를 볼 수 있다.
2008-12-29 21:42:04,390 ERROR [integration.anyframe.services.aop.service.UserServiceImpl]
User ID : test does not exist.
com.sds.emp.common.EmpException: User ID : test does not exist.
at integration.anyframe.services.aop.service.UserServiceImpl.getUser(UserServiceImpl.java:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
// ...
Exception in thread "main" com.sds.emp.common.EmpException: User ID : test does not exist.
at integration.anyframe.services.aop.service.UserServiceImpl.getUser(UserServiceImpl.java:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
// ...
Resources
다운로드
샘플 테스트 코드를 포함하고 있는 anyframe-aoptest-logging-src.zip 파일을 다운받은 후 위에서 제시한 예제 코드를 실행해 볼 수 있다.
| Name |
Download |
| anyframe-aoptest-src.zip |
Download
|