자바의 예외 처리에 대해 학습하세요
자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
자바가 제공하는 예외 계층 구조
Exception과 Error의 차이는?
RuntimeException과 RE가 아닌 것의 차이는?
커스텀한 예외 만드는 방법
에러(error)란 컴퓨터 하드웨어의 오동작 또는 고장으로 인해 응용프로그램에 이상이 생겼거나 JVM 실행에 문제가 생겼을 경우 발생하는 것을 말한다.
이 경우 개발자는 대처할 방법이 극히 제한적이다.
하지만 예외(Exception)은 다르다.
예외란 사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류를 말한다.
예외가 발생하면 프로그램이 종료가 된다는것은 에러와 동일하지만 예외는 예외처리(Exception Handling)을 통해 프로그램을 종료 되지 않고 정상적으로 작동되게 만들어 줄 수 있다.
자바에서 예외는 Try Catch문을 통해 해줄 수 있다.
예외 클래스의 구조다.
모든 예외 클래스는 Throwable 클래스를 상속받고 있으며, Throwable은 최상위 클래스 Object 의 자식이다.
Throwable을 상속받는 클래스는 Error와 Exception이 있다.
Error는 시스템 레벨의 심각한 수준의 에러를 의미한다.
Effective Java 에서는 이 클래스를 개발자가 상속 받지도 잡지도(catch) 말라고한다
반변에 Exception은 개발자가 로직을 추가하여 처리할 수 있다.
Exception은 수많은 자식 클래스를 가지고 있다.
그 중 우리는 RuntimeException
에 주목해야 한다.
RuntimeException은 CheckedException과 UnCheckedException을 구분하는 기준이다.
Exception의 자식 클래스 중 RuntimeException을 제외한 모든 클래스는 CheckedException이며, RuntimeException과 그의 자식 클래스들을 UnCheckedException이라 부른다.
Checked Exception이 발생할 가능성이 있는 메소드라면 반드시 try/catch로 감싸거나 throw로 던져서 처리해야 한다. 즉, 꼭 처리해야 하는 Exception이다.
반면에, Unchecked Exception은 명시적인 예외처리를 하지 않아도 된다.
이 예외는 피할 수 있지만 개발자가 부주의해서 발생하는 경우가 대부분이고, 미리 예측하지 못했던 상황에서 발생하는 예외가 아니기 때문에 굳이 로직으로 처리를 할 필요가 없도록 만들어져 있다.
try {
if ( accessToken != null ) {
// 유효한 토큰인지 확인
jwtTokenProvider.validateToken(accessToken);
this.setAuthentication(accessToken);
} else if ( refreshToken != null ) {
jwtTokenProvider.validateToken(refreshToken);
} else {
request.setAttribute("exception", ErrorCode.TOKEN_NULL);
}
} catch (SignatureException | SecurityException | MalformedJwtException e) {
request.setAttribute("exception", ErrorCode.WRONG_TYPE_TOKEN);
e.printStackTrace();
} catch (ExpiredJwtException e) {
request.setAttribute("exception", ErrorCode.EXPIRED_TOKEN);
e.printStackTrace();
} catch (UnsupportedJwtException e) {
request.setAttribute("exception", ErrorCode.UNSUPPORTED_TOKEN);
e.printStackTrace();
} catch (EmptyTokenException e) {
request.setAttribute("exception", ErrorCode.TOKEN_NULL);
} catch (IllegalArgumentException e) {
request.setAttribute("exception", ErrorCode.WRONG_TOKEN);
} catch (Exception e) {
e.printStackTrace();
}finally{
//예외발생여부에 관계없이 상항 수행되어야 하는 문장적는다.
}
filterChain.doFilter(request, response);
}
try 블록에는 예외가 발생할 수 있는 코드가 위치한다.
try 블록의 코드가 예외없이 정상 실행되면 catch 블록의 코드는 실행되지 않고 finally 블록의 코드를 실행한다.(finally 블록은 생략 가능하다)
try catch 문을 주로 사용하는 구간은 데이터베이스에 데이터를 주고 받을 때와 유효성 검증에서 많이 사용한다.
데이터베이스를 거쳐 올때나 유효성 검사를 할때는 변수가 많이 생기기 때문에 try catch문은 필수다.
그리고 finally 에는 데이터 베이스와의 연결을 끊어주는 코드를 주로 삽입한다.
하지만 지금은 데이터베이스 연결을 자동으로 끊어주기 때문에 사용하는 일은 거의 없다.
try->catch->finally 순으로, 발생 하지 않은 경우 try->finally 순으로 실행
throw 키워드를 사용하여 프로그래머가 고의로 예외를 발생시킬 수 있다.
예외던지기 Flow
1. try 안에서 연산자 new를 이용해서 발생시키려는 예외클래스의 객체생성.
2. 키워드 throw를 사용해서 생성한 예외를 catch 던진다.
3. catch 에서 던진 예외를 잡아서 확인 또는 처리한다.
public static void main (String[] args) {
try {
Exception e = new Exception("고의발생"); // 1. 예외 생성
throw e; // 2. 예외 던지기
} catch (Exception e) {
System.out.println("에러메세지:" + e.getMessage());
e.printStackTrace(); //예외정보 출력
}
System.out.println("프로그램 정상 종료.");
}
//결과
에러메세지: 고의발생
java.lang.Exception: 고의발생
at ExceptionEx9.main(ExceptionEx9.java:4)
프로그램 정상 종료.
예외가 발생한 메소드를 호출한 곳으로 예외 객체를 넘기는 방법.
자바의 예외 처리 방법은 예외가 발생한 지점에서 try-catch 혹은 try-catch-finally 라고 살펴봤다.
하지만 이 블럭을 사용하지 않아도 예외가 발생한 메소드를 호출한 지점으로 예외를 전달하여 처리하는 방법이 있다.
-> 이때 사용되는 예약어가 throws 이다.
public class Test{
static void callDriver() throws ClassNotFoundException{
Class.forName("oracle.jdbc.driver.OracleDriver");
System.out.println("완료");
}
public static void main(String[] args){
try{
callDriver();
}catch(ClassNotFoundException e){
System.out.println("클래스를 찾을 수 없습니다.");
}finally{
System.out.println("시스템 종료");
}
}
}
callDrvier() 메소드에서는 ClassNotFoundException 에 대한 처리를 직접 메소드 내에서 try/catch로 처리하지 않고 throws 하여 호출한 영역에서 처리하도록 한다.
커스텀 예외를 만들때 참고해야 할 4가지 Best Practices
그래서 예외 또한 문서화가 필요하다
/**
* The MyBusinessException wraps all checked standard Java exception and enriches them with a custom error code.
* You can use this code to retrieve localized error messages and to link to our online documentation.
*
* @author TJanssen
*/
public class MyBusinessException extends Exception { ... }
public List<OpportunityEntity> getOpportunityEntitiesFromExcelFile() throws IOException {
this.memberMap = getMemberMapFromRepository(memberRepository);
Sheet sheet = getSheet(sheetName);
if ( sheet == null ) {
throw new ExcelSheetNameNotFountException(ErrorCode.NOT_FOUND_SHEET);
}
Iterator<Row> rowIterator = sheet.rowIterator();
NOT_FOUND_SHEET(404, "엑셀 시트('opp')를 찾을 수 없습니다"),
Exception 과 RuntimeException은 예외의 원일을 기술하고 있는 Throwable
을 받을 수 있는 생성자 메소드를 제공한다.
만들고자 하는 커스텀 예외도 이렇게 하는 것이 좋다.
발생한 Throwable 를 파라미터를 통해 가져올 수 있는 생성자를 최소한 하나를 구현하고 수퍼클래스에 Throwable을 전달해줘야 한다.
ArithmeticException
정수를 0으로 나눌경우 발생
ArrayIndexOutOfBoundsExcetion
배열의 범위를 벗어난 index를 접근할 시 발생
ClassCastExcetion
변환할 수 없는 타입으로 객체를 반환 시 발생
NullPointException
존재하지 않는 레퍼런스를 참조할때 발생
IllegalArgumentException
잘못된 인자를 전달 할 때 발생
IOException
입출력 동작 실패 또는 인터럽트 시 발생
OutOfMemoryException
메모리가 부족한 경우 발생
NumberFormatException
문자열이 나타내는 숫자와 일치하지 않는 타입의 숫자로 변환시 발생