Where The Streets Have No Name

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

Developement/Java

Struts에서의 예외처리(1)

highheat 2007. 9. 19. 20:27
출처 : http://blog.naver.com/julymorning4/100018922231

자바의 예외는 애플리케이션에서 비정상적인 상황이 발생 할 때 생성되는 객체 입니다. 자바에서 예외가 발생하면java.lang.Throwable  객체를 상속한 객체를 생성하는 것입니다. Throwable 아래에는java.lang.Error와 java.lang.Exception등이 있습니다.

일반적으로 Exception의 경우 Application에서 처리 가능한 예외이며 스트럿츠에서의 모든 예외는 Exception 클래스의 서브 클래스로 생성하고 발생해야 합니다.

Exception을 제외한 Error 부분은 심각한 문제들을 위해 예약되어 있습니다. 예를 들어 애플리케이션에서 사용 할 시스템 메모리가 부족한에러 등이 해당 합니다. 그러므로 일반적으로 Error클래스의 서브 클래스의 경우는 애플리케이션에서 예외를 처리하기는 어려우며대부분 JVM 자체에서 Error또는 Error클래스의 서브 클래스의 인스턴스를 발생 시킵니다.

JVM은 각스레드의 호출 순서를 추적하는데 사용하기 위해 호출 스택(Call Stack)이라고 부르는 메소드 호출 스택(MethodInvocation Stack)을 사용 합니다. main()에서부터 출발하여 각각의 메소드가 호출 될 때 마다 메소드 호출스택의 상단에 추가되고 새롭게 추가된 메소드 스택이 현재 실행할 메소드가 되는 것 이며 각 메소드 필드 상태는 해당하는 스택프레임에 저장됩니다.

JVM은 자바 메소드가 정상적으로 종료하면 최상단의 현재 메소드의 스택 프레임을 꺼내고 다음스택 프레임을 실행 합니다. 그러나 예외가 발생 한다면 예외 핸들러를 찾는데 우선 현재 메소드에서 try-catch 구문이있는지 확인을 합니다. 만약 현재 예외가 발생 한 곳에 try-catch 구문이 없다면 JVM은 발생한 예외 혹은 그 예외의상위 예외를 처리하는 구문을 찾을 때 까지 호출 스택에서 메소드 프레임을 하나씩 꺼냅니다.

만약 JVM이 모든 스택프레임을 꺼냈는데도 해당 예외를 처리하는 구문이 없을 경우 예외가 발생 한 쓰레드는 종료하게 됩니다. 만약 종료한 쓰레드가 메인쓰레드이고 다른 비데몬 쓰레드가 없다면 애플리케이션 자체가 종료하게 됩니다.

이와는 달리 JVM이 예외를 처리하는 구문을 만난다면 그 구문이 있는 스택 프레임을 스택의 맨 위에 위치 시키고 다시 실행을 재개 하게 됩니다.

--------------------------
Checked와 Unchecked
--------------------------

자바의 예외는 checked 예외와 unchecked 예외로 구분해 볼 수 있습니다. Checked 예외는 클라이언트가 반드시처리해야 하는 것이며 이에 대한 처리를 위해 메소드에서는 try-catch로 처리하거나 throws을 이용하여 예외를 던져버려야 합니다.

JVM은 컴파일 시 checked 예외를 검사하지만 unchecked 예외에 대해서는 무시 합니다.Unchecked 예외로는 java.lang.ClassCastException등과 같은 예외가 있는데 일반적으로 부정확한로직이나 프로그래밍의 오류로 인해 발생 되는 오류들 입니다.

또한 checked와 unchecked 예외는 예외계층에서의 위치로도 결정 되는데 RuntimeException의 서브 클래스를 제외한 java.lang.Exception클래스의 자식 클래스는 모두 checked 예외 입니다. 즉 이 예외는 try-catch로 처리 하든지 아니면 throw 절에의해 처리되는 것이 보장 되어야 합니다.

Runtime 클래스와 그 자식 예외들은 unchecked 예외인데 이 예외는 try-catch 또는 throws 절에 위해 처리 되지 않아도 프로그램은 컴파일 되고 수행이 됩니다. 그러므로  unchecked라고 하는 겁니다.

보통 자바에서 try-carch문을 이용하는 것은 애플리케이션의 성능에 큰 영향을 주지 않습니다. 실제 예외가 발생 했을때만  영향을 미치는데 이러한 이유는 JVM이 예외를 처리할 핸들러를 찾기 때문 입니다. 만약 예외를 처리 할 catch 문이예외가 발생 한 메소드안에 있으면 성능에는 문제가 없지만 예외를 처리하기 위해 아래의 스택 프레임을 뒤진다면 성능에 영향을 줄수 있습니다.

따라서 try-catch문은 오류에 대한 처리 조건으로 이용되어야 하며 프로그램의 흐름을 컨트롤 하기 위해서 사용 되어져서는 안됩니다. 아래는 잘못 사용된 예 입니다.

Double basePrice = null;
String basePriceStr =request.getParameter( "BASE_PRICE_AMOUNT" );

// Use a try/catch to make sure the value is a number
try{
basePrice = Double.valueOf( basePriceStr );
}
catch( NumberFormatException ex )
{
// The value could not be converted to a valid Double; set the default
basePrice = ApplicationDefaults.DEFAULT_BASE_PRICE;
}


--------------------------
Chained 예외 사용하기
--------------------------

때에 따라서는 특정 타입의 예외를 catch한 후 다른 유형의 예외를 발생하는 경우가 있습니다. 예를 들어 스트럿츠에서애플리케이션의 Action 클래스를 호출해서 데이터베이스에 이미지를 저장 하는 경우라고 했을 때 Action 클래스에서는 다음과같은 update  메소드를 호출 합니다.

public void updateImageFile( String imagePath ) throws UploadException;

메소드를 호출하면 이미지 파일을 업로드 하고 예외가 발생하면 UploadException이 발생 합니다. 에러가 발생하는 실제오류를 보면 데이터베이스에 저장 공간이 없을 수도 있고 여러 가지 오류의 경우가 나올 수 있습니다. 아마도 최초 발생 하는예외는 IOException이나 SQLException등이 될 것 입니다.

그러나 사용자는 이 레벨의 오류를 알필요가 없습니다. 단지 필요한 것은 이미지의 업로드기 실패 했다는 것이 됩니다. 이런 경우 추후 개발자가 확인을 위해 시스템에로깅을 통해 SQLExceptio이나 IOException은 기록을 하고 사용자에게는 “입력 실패 입니다”와 같은 오류 메시지를보여 주어야 할 것 입니다.

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

Example 10-1. An exception class that supports chained exceptions
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* 이 클래스는 애플리케이션 예외의 슈퍼 클래스 입니다.
* 이 클래스와 이 클래스의 서브 클래스는 chained 예외 기능을 제공 합니다.
* chained 예외기능 이란 원래의 문제를 이 클래스나 이 클래스의 서브
* 클래스로 감싸서 다시 예외를 발생 시키는 것 입니다.
*/
public class BaseException extends Exception {
        protected Throwable rootCause = null;
        protected BaseException( Throwable rootCause ) {
                this.rootCause = rootCause;
        }

        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( );
        }
}

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

위예제는 원래의 Throwable을 감싸고 있으며 이러한 경우의 장점은 저 수준 예외에 대한 자세한 부분들을 추상화 할 수 있다는것입니다. 동시에 예외의 자세한 사항을 로그에 기록하고 게발자가 사용 할 수 있도록 할 수 있습니다. 이러한 예외들을 계속 연결할 수 있기 때문에 chained 예외라고 합니다.

“exception chaining”은 JDBC같은 저 수준의 오류를 사용자에게 전달하는 것을 막을 수 있습니다.


----------------------------
다중 예외
----------------------------

한 메소드에서 여러 예외가 발생 한다는 개념으로 “exception chaining”을 약간 변형 한 것입니다.

인터넷을 통해 회원가입을 하려고 합니다. 비밀번호와 비밀번호 확인이 잘못 되었으며 주소도 입력하지 않았고 전화번호도 기입하지않았습니다. 이 경우 여러 항목들을 입력하고 “저장” 버튼을 누르면 오류가 차례차례로 하나씩 나타나 사용자를 불편하게 할것입니다.

닷넷인 경우 Validation Check를 위한 객체를 지원하여 이러한 부분이 해결 가능 하지만 자바에서는 기본적으로 지원하지 않습니다. 불행이도 자바에서는 오직 하나의 Throwable만 던질 수 있습니다.

다중 예외를 처리하는 한가지 방법은 예외 클래스가 다른 예외들의 컬렉션을 지원하는 기본 예외를 갖도록 하는 것입니다. 아래의예문에서는 메소드가 실행 하는 동안 예외가 하나 이산 발생 하면 추가적인 예외들을 추가하여 클라이언트에 반환 합니다.

import java.util.List;
import java.util.ArrayList;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* exceptions라는 인스턴스 변수에서 여러 예외를 지원 합니다.
*/
public class BaseException extends Exception{
        protected Throwable rootCause = null;
        private List exceptions = new ArrayList( );

        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 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( );
        }
}