전자정부 표준프레임워크 기반의 시스템 개발시 Exception 처리, 정확히는 Exception 별 특정 로직(후처리 로직이라고 부르기도 함)을 흐를 수 있도록 하여
Exception 에 따른 적절한 대응이 가능도록 하고자 하는데 목적이 있다.
AOP 의 도움을 받아 비즈니스 POJO와 분리되어 After throwing advice 로 정의하였다.
AOP 관련한 내용은 AOP 모듈을 참조하길 바란다.
Exception 에 대해 이야기 하겠다.
Exception 이 발생시 Exception 발생 클래스 정보와 Exception 종류가 중요하다.
Exception 발생 클래스 정보와 Exception 종류는 모두 후처리 로직의 대상일지 아닐지를 결정하는데 사용된다.
전자정부 표준프레임워크 기반의 처리되는 Exception 은 현재 EgovBizException , DataAccessException , FdlException , 그외 나머지 로 구분되어 나누었다.
EgovBizException 은 Biz flow 상에 Biz Exception 으로 processException 메소드를 이용하여 아래와 같이 쓰인다.
public CategoryVO selectCategory(CategoryVO vo) throws Exception { CategoryVO resultVO = categoryDAO.selectCategory(vo); try { .... // 넘어온 resultVO 가 null 인경우 EgovBizException 발생 (result.nodata.msg 는 메세지 키에 해당됨) if (resultVO == null) throw processException("result.nodata.msg"); // 또는 throw processException("result.nodata.msg", 발생한 Excpetion ); return resultVO; }
DataAccessException 의 경우는 Spring 에서 persistence layer 에서 발생하는 Exception 에 대한 후처리를 위해 구분하였다.
FdlException 는 egovframe 내부에서 발생하는 Exception 을 위해 구분 해두었다.
FdlException 은 전자정부 표준프레임워크 에서 확장/추가된 영역에서 던져주는 Exception 이다.
그외 나머지 는 앞에서 나열한 Exception 을 제외한 Exception 이다.
Exception 을 발생한다는 것은 presentation layer 까지 Exception 을 던지겠다는 의도이다. 하지만 Exception 을 던지지 않고 Exception 후처리 로직처럼 수행후 계속 비즈니스로 돌아오는 방법도 필요할 것이다. 이것을 위해 leaveaTrace 메소드 가 존재한다. 파라미터로 존재하는 것은 메세지 키이다.
public CategoryVO selectCategory(CategoryVO vo) throws Exception { CategoryVO resultVO = categoryDAO.selectCategory(vo); try { //강제로 발생한 ArithmeticException int i = 1 / 0; } catch (ArithmeticException athex) { //Exception 을 발생하지 않고 후처리 로직 실행. leaveaTrace("message.trace.msg"); } return resultVO; }
우리는 앞에서 언급했던 Exception 후처리 방식과 Exception 이 아니지만 후처리 로직(leavaTrace)을 실행할 하는 방식에 대해 설명하도록 하겠다.
간략하게 보면
Exception 후처리 방식은 AOP(pointCut ⇒ after-throw) ⇒ ExceptionTransfer.transfer() ⇒ ExceptionHandlerService ⇒ Handler 순으로 실행된다.
LeavaTrace 는 AOP를 이용하는 구조는 아니고 Exception 을 발생하지도 않는다. 단지 후처리 로직을 실행하도록 하고자 함에 목적이 있다.
실행 순서는 LeavaTrace ⇒ TraceHandlerService ⇒ Handler 순으로 실행한다.
먼저 Exception Handling 에 대해 알아보도록 하자.
Exception 후처리와 leaveaTrace 설정을 위해서 샘플에서는 두개의 xml 파일을 이용한다. (context-aspect.xml, context-common.xml)
먼저 Exception 후처리를 위한 부분을 보겠다.
Exception Handling 을 위한 AOP 설정은 아래와 같다.
비즈니스 개발시 패키지 구조는 바뀌기 때문에 Pointcut은 egov.sample.service.*Impl.*(..)) 을 수정하여 적용할 수 있다.
ExceptionTransfer 의 property 로 존재하는 exceptionHandlerService 는 다수의 HandleManager 를 등록 가능하도록 되어 있다.
여기서는 defaultExceptionHandleManager을 등록한 것을 볼 수 있다.
context-aspect.xml
... <aop:config> <aop:pointcut id="serviceMethod" expression="execution(* egov.sample.service.*Impl.*(..))" /> <aop:aspect ref="exceptionTransfer"> <aop:after-throwing throwing="exception" pointcut-ref="serviceMethod" method="transfer" /> </aop:aspect> </aop:config> <bean id="exceptionTransfer" class="egovframework.rte.fdl.cmmn.aspect.ExceptionTransfer"> <property name="exceptionHandlerService"> <list> <ref bean="defaultExceptionHandleManager" /> </list> </property> </bean> <bean id="defaultExceptionHandleManager" class="egovframework.rte.fdl.cmmn.exception.manager.DefaultExceptionHandleManager"> <property name="patterns"> <list> <value>**service.*Impl</value> </list> </property> <property name="handlers"> <list> <ref bean="egovHandler" /> </list> </property> </bean> <bean id="egovHandler" class="egovframework.rte.fdl.cmmn.exception.handler.EgovServiceExceptionHandler" /> ...
defaultExceptionHandleManager는 setPatters() , setHandlers() 메소드를 가지고 있어 상단과 같이
등록된 pattern 정보를 이용하여 Exception 발생 클래스와의 비교하여 ture 인 경우 handlers 에 등록된 handler를 실행한다.
패턴 검사시 사용되는 pathMatcher 는 AntPathMatcher 를 이용하고 있다.
특정 pattern 그룹군을 만든후 patterns 에 등록하고 그에 해당하는 후처리 로직을 정의하여 등록할 수 있는 구조이다.
먼저 클래스에 대한 이해가 필요하다. 앞단에서 간단하게 설명을 했지만 다시 정리 하자면
Exception 발생시 AOP pointcut “After-throwing” 걸려 ExceptionTransfer 클래스의 transfer 가 실행된다.
transfer 메소드는 ExceptionHandlerManager 의 run 메소드를 실행한다. 아래는 구현예로 DefaultExceptionHandleManager 코드이다.
(구현시 필수사항) 상위클래스는 AbsExceptionHandleManager(또는 AbstractExceptionHandleManager)이고 인터페이스는 ExceptionHandlerService 이다.
구현되는 메소드는 run(Exception exception) 인 것을 확인할 수 있다.
DefaultExceptionHandleManager.java
public class DefaultExceptionHandleManager extends AbstractExceptionHandleManager implements ExceptionHandlerService { @Override public boolean run(Exception exception) throws Exception { log.debug(" DefaultExceptionHandleManager.run() "); // 매칭조건이 false 인 경우 if (!enableMatcher()) { return false; } for (String pattern : patterns) { log.debug("pattern = " + pattern + ", thisPackageName = " + thisPackageName); log.debug("pm.match(pattern, thisPackageName) =" + pm.match(pattern, thisPackageName)); if (pm.match(pattern, thisPackageName)) { for (ExceptionHandler eh : handlers) { eh.occur(exception, getPackageName()); } break; } } return true; } }
DefaultExceptionHandleManager 클래스는 목적이 설정을 통해 등록된 handler 클래스를 실행하는 것이다.
그렇다면 handler 클래스는 어떻게 구현되는지 알아보도록 하자.
실제로 Exception 후처리 로직은 handler 이다. 아래 클래스는 EgovServiceExceptionHandler 이다.
아래 코드는 log 만 남기고 있지만 실제로는 메일이나 다른 로직을 실행하도록 호출 할 수 있다.
EgovServiceExceptionHandler.java
public class EgovServiceExceptionHandler implements ExceptionHandler { protected Log log = LogFactory.getLog(this.getClass()); public void occur(Exception ex, String packageName) { log.debug(" EgovServiceExceptionHandler run..............."); try { log.debug(" sending a alert mail is completed "); } catch (Exception e) { e.printStackTrace(); } } }
시나리오 : CustomizableHandler 클래스를 만들어 보고 sample 패키지에 있는 Helloworld 클래스 Exception 시에 CustomizableHandler 를 실행한다.
먼저 CustomHandler 클래스를 아래와 같이 만든다.
ExceptionHandleManager 에서는 occur 메소드를 실행한다.
Handler 구현체는 반드시 (필수사항) ExceptionHandler Interface를 갖는다.
CustomizableHandler.java
public class CustomizableHandler implements ExceptionHandler { protected Log log = LogFactory.getLog(this.getClass()); public void occur(Exception ex, String packageName) { log.debug(" CustomHandler run..............."); try { log.debug(" CustomHandler 실행 ... "); } catch (Exception e) { e.printStackTrace(); } } }
CustomizableHandler 의 등록을 해보도록 하겠다.
역시서 주의해야 하는 부분은 patterns 에 sample 패키지에 있는 Helloworld 클래스 를 지정해주어야 한다.
<bean id="exceptionTransfer" class="egovframework.rte.fdl.cmmn.aspect.ExceptionTransfer"> <property name="exceptionHandlerService"> <list> <ref bean="customizableExceptionHandleManager" /> </list> </property> </bean> <bean id="customizableExceptionHandleManager" class="egovframework.rte.fdl.cmmn.exception.manager.DefaultExceptionHandleManager"> <property name="patterns"> <list> <value>**sample.Helloworld</value> </list> </property> <property name="handlers"> <list> <ref bean="customizableHandler1" /> <ref bean="customizableHandler2" /> <ref bean="customizableHandler3" /> </list> </property> </bean> <bean id="customizableHandler1" class="sample.CustomizableHandler" /> <bean id="customizableHandler2" class="sample.CustomizableHandler" /> <bean id="customizableHandler3" class="sample.CustomizableHandler" />
이런식으로 여러개의 Handler를 등록해줄 수 있다.
... <bean id="leaveaTrace" class="egovframework.rte.fdl.cmmn.trace.LeaveaTrace"> <property name="traceHandlerServices"> <list> <ref bean="traceHandlerService" /> </list> </property> </bean> <bean id="traceHandlerService" class="egovframework.rte.fdl.cmmn.trace.manager.DefaultTraceHandleManager"> <property name="patterns"> <list> <value>*</value> </list> </property> <property name="handlers"> <list> <ref bean="defaultTraceHandler" /> </list> </property> </bean> <bean id="antPathMater" class="org.springframework.util.AntPathMatcher" /> <bean id="defaultTraceHandler" class="egovframework.rte.fdl.cmmn.trace.handler.DefaultTraceHandler" /> ...
Interface TraceHandler를 아래와 같이 implements 한다.
package egovframework.rte.fdl.cmmn.trace.handler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class DefaultTraceHandler implements TraceHandler { public void todo(Class clazz, String message) { //수행하고자 하는 처리로직을 넣는 부분... System.out.println(" log ==> DefaultTraceHandler run..............."); } }
사용방법을 다시 상기 해보면 아래와 같다.
메세지키(message.trace.msg) 를 이용하여 메세지 정보를 넘겨 Handler 를 실행한다.
public CategoryVO selectCategory(CategoryVO vo) throws Exception { CategoryVO resultVO = categoryDAO.selectCategory(vo); try { //강제로 발생한 ArithmeticException int i = 1 / 0; } catch (ArithmeticException athex) { //Exception 을 발생하지 않고 후처리 로직 실행. leaveaTrace("message.trace.msg"); } return resultVO; }