Where The Streets Have No Name

Struts에서의 예외처리(2) 본문

Developement/Java

Struts에서의 예외처리(2)

highheat 2007. 9. 19. 20:29
출처 : http://blog.naver.com/julymorning4/100018922244

스트럿츠가 제공 하는 예외 처리

스트럿츠 1.1.이후에는 작지만 효율적인 예외처리 프레임워크를 추가 했습니다. 또한org.apache.struts.util.AppException 클래스는 ActionError를 포함하며java.lang.Exception을 확장한 클래스 입니다.

아래와 같이 스트럿츠에서 사용하면 되는데

throw new AppException(“error.password.invalid”);

생성자의 “error.password.invalid”는 리소스 번들의 key 이며 프레임웍에서는 자동적으로 예외의ActionError 객체를 생성하고 적절한 scope에 저장 합니다. 물론 애플리케이션에서 AppException에 대해확장이 가능 합니다.

----------------------------------------------------
선언적 예외 처리와 프로그램적 예외처리
----------------------------------------------------

선언적 예외처리는 struts-config.xml등에 예외에 대해서 정의 하는 것 입니다. 이에 반해 프로그램적 예외 처리는선언적 예외 처리와 정반대되는 개념으로 애플리케이션에서 정의한 내부 코드를 통해 예외를 처리하는 전통적인 방법 입니다.

아래는 login Action에서 발생하는 세가지 예외를 정의한 것 입니다.

<action-mappings>
                <action
                        path="/login"
                        type="com.oreilly.struts.storefront.security.LoginAction"
                        name="loginForm"
                        scope="request"
                        input="/login.jsp">
                
                <!--The following exceptions can be thrown during the login action -->
                <exception
                        key="security.error.changepassword"
                        path="/changePassword.jsp"
        type="com.oreilly.struts.framework.exceptions.ExpiredPasswordException"/>

                <exception
                        key=" security.error.loginfailed"
                type="com.oreilly.struts.framework.exceptions.InvalidLoginException"
                        path="/login.jsp"/>

                <exception
                        key="security.error.accountlocked"
        type="com.oreilly.struts.framework.exceptions.AccountLockedException"
                        path="/accountLocked.jsp"/>
        </action>
</action-mappings>


위에서 exception 요소는 정의한 예외가 발생 할 경우 포워드할 경로를 액션 매핑이나 전역 예외에 정의 합니다. LoginAction이 실행하는 동안 ExpiredPasswordException이 발생 한다면 컨트롤러의 제어를changePassword.jsp로 포워드 됩니다.

예외를 Action 클래스에 프로그램 코드를 통해 코딩을 하지않았다면 RequestProcessor는 정의한 예외 타입을 설정한 exception 요소가 있는지 확인 하며 만약exception 요소가 있다면 컨트롤러는 exception 요소의 path 속성에 지정된 자원으로 포워드 합니다.

아래는 RequestProcessor 클래스의 processException() 메소드 입니다. 메소드의 시작 시점에findException() 메소드가 ExceptionConfig 객체를 반환 하는데 ExceptionConfig 객체는 설정파일에 기술된 exception 요소가 메모리에 있는 것으로 생각하면 됩니다.

만약 findException()메소드가 발생한 예외와 대응하는 exception 요소를 찾지 못하면 스트럿츠 프레임워크에서의 예외 처리 없이 클라이언트에 반환됩니다. 발생한 예외가 IOException이나 IOException 클래스의 서브 클래스가 아니면ServletException 인스턴스로 감싸서 다시 던집니다.

만약 특정한 예외를 정의한 Action Mapping이 있다면 findException() 메소드를 통해 ExceptionConfig 객체를 반환 합니다.

getHandler() 메소드는 ExceptionConfig 객체를 추출하고 추출한 핸들러를 예외를 처리하는데 사용 합니다.

--------------------------------------------------------
protected ActionForward processException(HttpServletRequest        request,
                                        HttpServletResponse response,
                                        Exception exception,
                                        ActionForm form,
                                        ActionMapping mapping)
                                        throws IOException, ServletException {
        // Is there a defined handler for this exception?
        ExceptionConfig config =mapping.findException(exception.getClass( ));

        if (config == null){
                if (log.isDebugEnabled( )){
                        log.debug(getInternal().getMessage("unhandledException",exception.getClass( )));
                }

        if (exception instanceof IOException){
                throw (IOException) exception;
        }
        else if (exception instanceof ServletException){
                throw (ServletException) exception;
        }
        else{
                throw new ServletException(exception);
        }


        // Use the configured exception handling
        try {
                Class handlerClass = Class.forName(config.getHandler( ));
                ExceptionHandler handler =(ExceptionHandler)handlerClass.newInstance( );
                return (handler.execute(exception, config, mapping,        form,request, response));
        }
        catch (Exception e){
                throw new ServletException(e);
        }
}


---------------------------------------------------------

스트럿츠 프레임워크에서는 예외 처리에 관한 환경 설정이 되어 있지 않은 경우 사용 가능한 기본 예외 처리 클래스를 포함 하고있는데. org.apache.struts.action.ExceptionHandler가 기본 핸들러 입니다.

기본핸들러의 execute() 메소드는 ActionError를 생성하고 적절한 scope에 저장한 다음 exception 요소의path 속성에 할당 되어 있는 ActionForward 객체를 반환 합니다. 결국 Actionforward의 경로로 제어를넘기게 됩니다.

예외가 발생 했을 때 다른 처리를 원한다면 exception 요소는 핸들러 클래스에 대한오버라이드를 허용 합니다. 즉 struts-config.xml 파일에 exception 요소 안에서 handler 속성에org.apache.struts.action.ExceptionHandler 클래스를 상속하는 클래스를 명시 함으로서 가능합니다. Handler 클래스의 execute() 메소드를 오버라이드 함으로서 각각의 애플리케이션들은 기본 예외에서 확장된 예외처리를 할 수 있습니다.



------------------------------------------------------------

import java.util.List;
import java.util.ArrayList;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* 이 클래스는 애플리케이션 예외의 공통 슈퍼 클래스 입니다.
* 이 클래스와 이 클래스의 서브 클래스는 chained exception 기능을 제공
* chained exception 기능은 원래 문제를 이 클래스나 이 클래스의 서브 클래스로
* 감싸서 다시 실행 할 수 있습니다.
* 이 클래스는 exception을 List로 관리 함으로서 다중 예외 처리가 가능 합니다.
*/
public class BaseException extends Exception{
        protected Throwable rootCause = null;
        
        //예외를 여러 개 관리하고 나중에 ActionError를 만들 때도 반영
        private List exceptions = new ArrayList( );

        private String messageKey = null;
        private Object[] messageArgs = null;

        public BaseException( ){
                super( );
        }
        
        //생성자
        public BaseException( Throwable rootCause ) {
                this.rootCause = rootCause;
        }

        public List getExceptions( ) {
                return exceptions;
        }

        public void addException( BaseException ex ){
                exceptions.add( ex );
        }

        public void setMessageKey( String key ){
                this.messageKey = key;
        }

        public String getMessageKey( ){
                return messageKey;
        }
        
        //어떤 메시지는 아규먼트가 여러 개 일 수 있습니다.
        // 예를들면 나이는 0 ~ 99 사이의 수가 들어와야 합니다.
        public void setMessageArgs( Object[] args ){
                this.messageArgs = args;
        }

        public Object[] getMessageArgs( ){
                return messageArgs;
        }

        public void setRootCause(Throwable anException) {
                rootCause = anException;
        }

        public Throwable getRootCause( ) {
                return rootCause;
        }

        public void printStackTrace( ) {
                printStackTrace(System.err);
        }

        public void printStackTrace(PrintStream outStream) {
                printStackTrace(new PrintWriter(outStream));
        }

        public void printStackTrace(PrintWriter writer) {
                super.printStackTrace(writer);
                if ( getRootCause( ) != null ) {
                        getRootCause( ).printStackTrace(writer);
                }
                writer.flush( );
        }
}



-------------------------------------------------------------

아래에서 messageKey는 ActionError 클래스의 생성자에 전달되고 스트럿츠 프레임웍에서는 키와 리소스 번들의 메시지를 대응 시킵니다. 또한 클래스는 생성된 예외들을 추가 할 수 있는 객체 배열을 포함합니다.

객체 배열에 있는 예외 객체들은 MessageFormat을 기반으로 파라미터에 따라 리소스 번들의 메시지를 교환 할 수 있습니다. 번들 안에 있는 메시지는 다음과 같습니다.

global.error.invalid.price=The price must be between {0} and {1}.

아래는 기본 예외 핸들러 클래스를 확장 하고 ActionError 생성자 내부의 인자를 동적으로 생성하는 기능을 제공 합니다.

-----------------------------------------------------------------

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ExceptionHandler;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionError;
import org.apache.struts.util.AppException;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.ExceptionConfig;
import com.oreilly.struts.framework.exceptions.BaseException;

public class SpecialExceptionHandler extends ExceptionHandler
{
        protected ActionForward execute(Exception ex,
                        ExceptionConfig config,
                        ActionMapping mapping,
                        ActionForm formInstance,
                        HttpServletRequest request,
                        HttpServletResponse response)
                                        throws ServletException {
                ActionForward forward = null;
                ActionError error = null;
                String property = null;

                /* input 속성이나 exception 요소에서 포워드할 path를 가져옴 */
                String path = null;
                if (config.getPath( ) != null) {
                        path = config.getPath( );
                }
                else{
                        path = mapping.getInput( );
                }

                // Construct the forward object
                forward = new ActionForward(path);

                /* Figure out what type of exception has been thrown.The Struts
                * AppException is not being used in this example.
                */
                if( ex instanceof BaseException) {
                        // ********* 특화된 처리 부분
                        BaseException baseException = (BaseException)ex;
                        String messageKey = baseException.getMessageKey( );
                        Object[] exArgs = baseException.getMessageArgs( );
                        if ( exArgs != null && exArgs.length > 0 ){
                        // If there were args provided, use them in the        
ActionError
                                error = new ActionError( messageKey, exArgs );
                        }
                        else{
                                // Create an ActionError without any arguments
                                error = new ActionError( messageKey );
                        }
                }
                else{
                        error = new ActionError(config.getKey( ));
                        property = error.getKey( );
                }

                // Store the ActionError into the proper scope
                // The storeException method is defined in the parent        class
                storeException(request, property, error, forward,
                config.getScope( ));
                return forward;
        }
}


스트럿츠의 예외 처리에 대해 정리해보면 Action에서 execute() 메소드를 실행 시 예외처리를 위해 try-catch로쌉니다. 예외가 발생 했다면 RequestProcessor 클래스의 findException이 호출되어ExceptioConfig 객체를 반환 합니다. 다음으로 getHandler() 메소드가 호출 되어 핸들러를 얻습니다. 다음으로기본 핸들러만 정의되어 있다고 보면 핸들러 클래스의 execute() 메소드가 호출되고 여기에서 ActionError를 생성 후적절한 scope에 저장 후 path 속성에 정의된 리소스로 제어를 넘깁니다.




-------------------------------
프로그램에서 오류 처리하기
-------------------------------

Action 클래스에서 예외가 발생 했을 때 만약 발생한 예외가 애플리케이션 예외 인 경우 로그를 남기고 ActionError를 생성하여 해당 스코프에 저장한 후 ActionForward를 통해 제어를 넘기는 식으로 처리 합니다.

예외의 효과적인 관리를 위해 최상위 래퍼 클래스(BaseException)를 하나 만들고 애플리케이션 예외를 모두 그 안에 두는것 입니다. 이렇게 하면 catch 문에서 BaseException만 받아내면 될 것 입니다. (BaseException이아니면 시스템 예외라고 가정 할 수 있으며 이렇게 처리 해야 합니다.)

시스템 예외 처리 방법은 예외를 로그로 남기고 시스템 에러 페이지를 만들어 그곳으로 포워드 시키면 됩니다.

아래는 Action안에서의 처리 입니다.

try{
        // Peform some work that may cause an application or system        exception
}
catch( BaseException ex ){
        // Log the exception
        // Create and store the action error
        ActionErrors errors = new ActionErrors( );
        ActionError newError = new ActionError( ex.getErrorCode(),ex.getArgs( ) );
        errors.add( ActionErrors.GLOBAL_ERROR, newError );
        saveErrors( request, errors );

        // Return an ActionForward for the Failure resource
        return mapping.findForward( "Failure" )
}
catch( Throwable ex ){
        // Log the exception
        // Create and store the action error
        ActionError newError = new        ActionError( "error.systemfailure" );
        ActionErrors errors = new ActionErrors( );
        errors.add( ActionErrors.GLOBAL_ERROR, newError );
        saveErrors( request, errors );
        // Return an ActionForward for the system error resource
        return mapping.findForward( IConstants.SYSTEM_FAILURE_PAGE );
}

Action마다 이렇게 한다는 것은 중복되는 코드가 발생 할 수 있는데 앞에서 설명 드린 선언적 접근 방법을 이용한다면 해결 할 수 있지만 Action의 최상위인 BaseAction을 하나 만들어 이를 해결 할 수 있습니다.

모든 action에 공통적으로 들어가야 할 기능이 있다면 (로그인 체크,접속/비 접속 체크,쇼핑 카트 등) 그것을 구현해놓고(추상 클래스로), 다른 action들이 struts의 action을 상속받지 않고, 이 공통 action을 상속받게합니다. 이것은 필수요소가 아니라 선택사항입니다. 공통사항이 없다면 하지 않아도 상관 없습니다.

아래에 최상위 Action에 관련된 예문이 있으니 참고 바랍니다.

//execute( ) method of the BaseAction(Action의 최상위 클래스로서 추상클래스 입니다.)
//아래의 경우 예외 발생시 execute 메소드의 catch에 의해 잡힙니다.
publicActionForward execute(ActionMapping mapping,ActionFormform,HttpServletRequest request,HttpServletResponse response) throwsException {
ActionForward forwardPage = null;
        try{
                UserContainer userContainer = getUserContainer( request );
                // 이부분에 필요하다면 모든 액션들이 공통으로 확인해야 되는 사항이
                //있다면 처리 합니다.(로그인 여부, 장바구니 확인 등)

        // 별도로 선언된 executeAction을 실행 시킵니다.
        // executeAction은 서브 액션의 입맛에 맞게 적절히 구현해서 사용 합니다.
        //예를들면 게시판 기능이라면 게시물을 읽는 기능, 쓰는 기능, 삭제 기능, 수정 기능 등이  될 수 있습니다.
                forwardPage = executeAction(mapping, form, request,        response, userContainer);
        }
        //아래의 catch문이 모든 Action에 있어야 하는 겁니다.
        catch (BaseException ex){
                // Log the application exception using your logging        framework
                // Call the generic exception handler routine
                forwardPage = processExceptions( request, mapping,        ex );
        }catch (Throwable ex){
                // Log the system exception using your logging framework
                // Make the exception available to the system error page
                request.setAttribute( Action.EXCEPTION_KEY, ex );

                // Treat all other exceptions as system errors
                forwardPage =mapping.findForward( IConstants.SYSTEM_FAILURE_KEY );
        }
        return forwardPage;
}

//추상메소드로 선언하고 이 최상위 액션을 상속 받는 하위 액션들의 입맛에 맞게 구현토록 합니다.
abstract public ActionForward executeAction( ActionMapping mapping,
                                                                                        ActionForm form,
                                                                                        HttpServletRequest request,
                                                                                        HttpServletResponse response,
                                                                                        UserContainer userContainer )
throws BaseException;


다음은 execute() 메소드에서 예외가 발생 되었을 때 이것을 처리하기 위한 BaseAction의processException() 메소드를 보도록 합니다.

-----------------------------------------------------------

Action안에 정의 되어 있습니다. 이 메소드는 여러 개일지 모르는 예외를 찾아 예외의 수 만큼 processBaseException이라는 메소드를 다시 불러 그곳에서 ActionError등에 저장 하는 기능 등을 수행하도록 합니다.

protected ActionForward processExceptions( HttpServletRequest request,
                                        ActionMapping mapping,
                                        BaseException ex )
{
        ActionErrors errors = new ActionErrors( );
        ActionForward forward = null;

        // 사용자의 지역 설정을 가지고 옵니다.
        Locale locale = getUserContainer( request ).getLocale( );
        if (locale == null){
                        // 지역이 설정되지 않았다면 기본 지역을 사용
                        environment locale = Locale.getDefault( );
        }

        processBaseException(errors, (FieldException) ex, locale);

        // 입력된 리소스와 failure 포워드를 반환 합니다.
        String inputStr = mapping.getInput( );
        String failureForward = mapping.findForward(IConstants.FAILURE_KEY);

        if ( inputStr != null) {
                forward = new ActionForward( inputStr );
        }
        else if (failureForward != null){
                forward = failureForward;
        }

        // 예외가 하위 예외를 포함하고 있는지 확인
        List exceptions = ex.getExceptions( );

        if (exceptions != null && !exceptions.isEmpty( ) ){
                int size = exceptions.size( );
                Iterator iter = exceptions.iterator( );

                while( iter.hasNext( ) ){
                        // 모든 하위예외들은 BaseException이어야 합니다.
                        BaseException subException =(BaseException)iter.next( );
                        processBaseException(errors, subException, locale);
                }
        }

        // Tell the Struts framework to save the errors into the request
        saveErrors( request, errors );

        // Return the ActionForward
        return forward;
}


------------------------------------------------------------

processException 메소드의 수행 과정은 다음과 같습니다.

1.        사용자의 지역 확인
2.        Top레벨 예외의 processBaseException() 메소드를 수행
3.        다른 서브 예외가 있다면 각각의 예외를 수행
4.        생성한 모든 ActionError를 저장
5.        제어를 input 속성에 있는 리소스나 액션에 설정된 “Failure” Actionforward에 넘김


아래는 BaseAction의 processBaseException 메소드 입니다.


protected void processBaseException( ActionErrors errors,
                                                                        BaseException ex,
                                                                        Locale locale)
{

        // 추가될 ActionError의 레퍼런스 저장
        ActionError newActionError = null;

        // 에러 코드는 리소스 번들의 키값
        String errorCode = ex.getMessageKey( );

        /*MessageFormat 객체가 사용하는 추가적인 인자가 있다면
        * args에 예외를 추가
        */
        Object[] args = ex.getMessageArgs( );

        
        // ACtionError 클래스의 인스턴스 생성자
        if ( args != null && args.length > 0 ){
                // Use the arguments that were provided in the exception
                newActionError = new ActionError( errorCode, args );
        }
        else{
                newActionError = new ActionError( errorCode );
        }

        errors.add( ActionErrors.GLOBAL_ERROR, newActionError );
}