Anyframe Web의 Exception Handling

Anyframe에서 공통으로 사용하는 BaseException을 포함한 Exception이 throw 되었을 때, 이를 처리하는 ExceptionHandler에 대해 알아본다.


Anyframe Web에서는 Action Class에서 try-catch 문으로 exception 처리를 코딩하지 않고 struts-config.xml 에 선언적으로 exception handling 을 기술한다.
DefaultExceptionHndler
DefaultExceptionHndler는 Struts의 ExceptionHandler를 확장한 클래스로서 Anyframe에서 공통으로 사용하는 BaseException이 catch 되었을때, BaseException에 정의된 message key와 대체문자열을 ActionMessage에 저장하여 Forward 하는 로직이 추가된 클래스이다. 일반 Exception이 catch되었을 때는 Struts의 기본 Exception Handler와 같이 exception handler 설정(struts-config.xml)에 정의된 key값으로 ActionMessage를 설정하여 Forward하게된다. 에러 메시지 처리는 Tag Library를 이용하여 메시지key를 참조하게 된다. 단 이 때는 해당 key에 대한 Web 단의 MessageResource 에 해당 key에 대한 메시지 맵핑이 반드시 존재해야 한다.
DefaultBaseExceptionHandler
Anyframe 서비스 구현 시 기본 Exception인 BaseException 은 비즈니스 Exception 생성 시 MessageSource Bean과 관련된 에러 메시지 Key를 전달하여, 에러 메시지 정의 시 해당 에러에 대해 유형별(기본,해결책,원인) 메시지 값을 비지니스 레이어에서 정의하도록 가이드하고 있으며 Exception 발생 시 기본 메시지 키와 관련된 메시지를 모두 추출한 후 Message 객체에 담게 된다. (온라인 매뉴얼의 Technical Services 활용 > common > Exception Handling 참조)
DefaultBaseExceptionHandler 는 이러한 BaseException 에서 유형별 메시지를 모두 처리할 수 있게끔 제공되므로 대부분의 경우 DefaultBaseExceptionHandler를 사용하는 것이 좋다. 메시지 Key를 돌려주어 Tag Library로 에러 출력을 하는 형태가 아닌 유형별 메시지 자체를 설정하고 있으며 비지니스 Exception 외에 Web 단에서 발생하는 Exception에 대해서는 Web 단의 message-resources 메시지 처리가 가능토록 defaultBundle 명 "anyframe.web.struts.common.CommonResource" 를 참조할 수 있다. 보통의 경우 ExceptionHandler 는 제공되는 DefaultBaseExceptionHandler를 extends 하여 특정 Exception 처리가 가능토록 override 하거나 page navigation 제어를 추가 하는 처리 등이 필요하며 이 때 Web단의 message-resource 를 참조할 필요가 있는 경우 defaultBundle 명을 재정의하여 처리하면 된다.

선언적인 Exception Handling

    Anyframe Web에서는 각 Action Class에서 try-catch 문으로 exception을 코딩하지 않고, struts-config.xml에서 선언적으로 exception 을 handling하는 것을 권장한다. 다음은 struts-config.xml의 선언적인 exception 처리의 한 예제소스이다.

    Samples

    <action
    	name="userForm"
    	path="/empLogin"
    	type="com.sds.emp.user.web.LogInAction"
    	scope="request"
    	validate="false">
    	<exception
    		handler="anyframe.web.struts.common.util.DefaultBaseExceptionHandler"
    		type="anyframe.common.exception.BaseException"
    		key="common.msg.global.error"/>
    	<forward name="success" path="/home.do"  />
    	<forward name="failure" path="/empLoginView.do"  />
    </action>
    위의 설정은 Action을 수행 중에 Exception이 발생하고, 발생한 Exception이 BaseException Type일 경우에 DefaultBaseExceptionHandler 클래스를 사용하여 Exception을 Handling하게 된다.
    아래와 같이 global-exceptions 에 일반적인 Exception에 대한 처리를 공통으로 적용할 수도 있다. 예에서는 DefaultBaseExceptionHandler를 확장한 EmpExceptionHandler가 사용되고 있다. 같은 Exception 정의가 있는 경우 개별 action 에 정의된 Exception 처리가 우선권이 있다.
    <global-exceptions>
            <exception 
            	path="/sample/common/error.jsp" 
            	key="error.common.msg.authentication" 
            	type="anyframe.web.struts.common.util.AuthenticationException"
            	handler="com.sds.emp.common.EmpExceptionHandler" />
            <exception 
            	path="/sample/common/error.jsp" 
            	key="error.common.msg.authorization" 
            	type="anyframe.web.struts.common.util.AuthorizationException"
            	handler="com.sds.emp.common.EmpExceptionHandler" />
            <exception 
            	path="/sample/common/error.jsp" 
            	key="common.msg.invalidtoken.error" 
            	type="anyframe.web.struts.common.util.InvalidTokenException"
            	handler="com.sds.emp.common.EmpExceptionHandler" />
            <exception key="error.common.msg" 
            	path="/sample/common/error.jsp" 
            	type="com.sds.emp.common.EmpException" 
            	handler="com.sds.emp.common.EmpExceptionHandler" />
            <exception key="error.common.msg" 
            	path="/sample/common/error.jsp" 
            	type="java.lang.Exception" 
            	handler="com.sds.emp.common.EmpExceptionHandler" />
    </global-exceptions>

DefaultBaseExceptionHandler의 확장포인트

    다음은 DefaultBaseExceptionHandler 소스의 일부이다.
    protected String defaultBundle = "anyframe.web.struts.common.CommonResource";
    ..
    public ActionForward execute(Exception ex, ExceptionConfig ae,
            ActionMapping mapping, ActionForm formInstance,
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException {
    
      ActionForward forward = null;
      if (ae.getPath() != null) {
         forward = new ActionForward(ae.getPath());
      } else {
          forward = mapping.getInputForward();
      }
    
      try {
        if (ex instanceof BaseException) {
    	BaseException baseEx = (BaseException) ex;
    	request.setAttribute(Globals.EXCEPTION_KEY, ex);
    	logException("emp exception is occurred! ", ex);
    	String[] messages = new String[2];
    	messages[0] = baseEx.getMessages().getUserMessage();
    	messages[1] = baseEx.getMessages().getSolution();
    	storeException(request, messages, forward, ae.getScope());
        } else {
    	request.setAttribute(Globals.EXCEPTION_KEY, ex);
             logException("runtime exception is occurred! ", ex);
    	MessageResourcesFactory factory = MessageResourcesFactory.createFactory();
    	MessageResources messageResources = factory.createResources(defaultBundle);
            	if(ex instanceof ModuleException) {
            	    ModuleException moduleEx = (ModuleException) ex;
    	    ActionMessage actionMessage = moduleEx.getActionMessage();
    	    String messageKey = actionMessage.getKey();
    	    String[] messages = new String[2];
    	    String[] args = {""};
    	    messages[0] = messageResources.getMessage(Locale.getDefault(),
    	    messageKey, args);
    	    messages[1] = messageResources
    	                   .getMessage(Locale.getDefault(), messageKey + ".solution", args);
    	    storeException(request, messages, forward, ae.getScope());
            } else {
                 String[] messages = new String[3];
    	    String[] args = {""};
    	    messages[0] = messageResources.getMessage(Locale.getDefault(), ae.getKey(), args);
    	    messages[1] = "";	// solution is empty
    	    messages[2] = ex.getMessage();	// reason is exception message
    	    storeException(request, messages, forward, ae.getScope());
            }
        }
    … 하략 …
    DefaultBaseExceptionHandler에서는 exception 발생시 path를 지정해 주지 않으면 해당 request의 input path로 forward된다. 이때 forward되는 jsp에서는 forword된 메시지 스트링 배열을 보여주어 에러메세지를 화면에 display할 수 있다. 위에서 기본으로 BaseException인 경우 기본,해결책 메시지(1, 2번째 문자열)를 담고 있으며, Struts의 ModuleException 인 경우 기본 messageResources 인 anyframe.web.struts.common.CommonResource 에서 message key와 solution key를 찾아 해당 메시지를 설정하고 그 외의 runtime Exception인 경우 stuts-config 파일에 정의한 해당 Exception의 에러 Key에 대응되는 messageResources 의 메시지와 Exception 자체의 에러메시지를 원인 메시지 (3번째 문자열)로 담고 있다.

    Samples


    다음은 DefaultBaseExceptionHandler를 확장하여 프로젝트 자체의 Exception 처리를 재정의한 예이다.
    public class EmpExceptionHandler extends DefaultBaseExceptionHandler {
    
    public EmpExceptionHandler() {
    	this.defaultBundle = "com.sds.emp.EmpResources";
    }
    	
    public ActionForward execute(Exception exception, ExceptionConfig config,
    	ActionMapping mapping, ActionForm form, HttpServletRequest request,
    	HttpServletResponse response) throws ServletException {
    
    	ActionForward forward = mapping.getInputForward();
    	String forwardPath = forward.getPath();
    	if (forwardPath == null || forwardPath.equals("")) {
    		request.setAttribute("authFail", "true");
    	}
    	String url = "/empLoginView.do" + "?";
    	Enumeration enumrequest = request.getParameterNames();
    	while (enumrequest.hasMoreElements()) {
    		String parameterName = (String) enumrequest.nextElement();
    		String parameterValue = request.getParameter(parameterName);
    		url += parameterName + "=" + parameterValue + "&";
    	}
    	forward.setPath(url);
    	request.getSession().setAttribute("afterErrorPage", forward);
    
    	return super.execute(exception, config, mapping, form, request, response);
    	}
    }
    defaultBundle을 해당 프로젝트의 Web 단 MessageResource인 com.sds.emp.EmpResources 로 재정의 하였고 request의 파라메터들을 get 방식으로 url string에 더하여 afterErrorPage로 포워딩하도록 로직이 추가되어 있다. Exception Type 에 따른 처리는 super.execute 메소드를 그대로 사용하고 있음을 확인할 수 있다.
    아래는 해당 ExceptionHandler가 설정한 에러메시지를 처리하는 error.jsp 의 예이다.
    <%
    ...
    	String[] messages = (String[])request.getAttribute(Globals.MESSAGE_KEY);
      	
    	String userMessage = messages[0];
      	String solution = "";
      	String reason = "";
      	
      	if(messages.length==2) {
      		solution = messages[1];
      	}
      	
      	if(messages.length==3) {
      		solution = messages[1];
      		reason = messages[2];
      	} 
    %>
    ...
    	<%= userMessage %><p/>
    	<% if(!solution.equals("")) { %>
    		<strong>* SOLUTION</strong><br/>
    		<%= solution %>
    	<% } %>
    	<% if(!reason.equals("")) { %>
    		<strong>* REASON</strong><br/>
    		<%= reason %>
    	<% } %>
    ...
    <td background="<html:rewrite page='/sample/images/ct_btnbg02.gif'/>" 
        class="ct_btn01" style="padding-top:3px;">
        <a href="javascript:fncGoAfterErrorPage();">확인</a>
    </td>
    ...
    이와 같이 각 사이트의 Exception 처리 기준에 맞게 기본 ExceptionHandler를 확장하여 사용하도록 한다.
    DefaultExceptionHandler인 경우 ActionMessage로 에러메시지를 넘기고 에러출력용 jsp 에서는 Tag Library를 사용할 수 있으며 대체문자열에서도 Resource Properties를 이용할 수 있도록 메시지 태크를 확장하였다. 다음 장에서는 DefaultMessages Tag에 대하여 알아본다.

Resources