예외처리(Exception)
- 구현된 프로그램 로직에서 발생하는 오류를 의미
-
발생하는 Exception을 적절하게 처리해야 프로그램이 종료되지 않고 원활히 돌아가게 할 수 있다
-
Exception
VSError
-
Exception
(예외) : 구현한 로직에서 발생. 처리가능 -
Error
(에러) : 시스템레벨에서 발생. 처리불가능
-
- Error는 catch로 잡으려고 해서는 안되기 때문에
Throwable
을 catch로 잡으면 안된다-
Error
가 하위에 속해 있다
-
Exception 종류
-
CheckedException
는 복구가 가능할 떄RuntimeException
,Error
는 복구가 불가능할 때 사용
1. Checked Exception
- 컴파일시 체크되는 exception ->
Exception
상속- 반드시 예외처리가 되어야한다
- 예외를 던질 시 반드시
throws
로 던져야 함
- 예외발생시 트랜잭션을
commit
함-
CheckedException
은 예외처리가 강제되기 때문에 이 과정에서 복구가 가능하다고 생각하는 메커니즘이다 - 복구가 되었기 때문에
commit
을 하는 것으로 추측한다 ex)ClassNotFoundException
-
문제점
- 복구 불가능한 예외
- 반드시 처리해야 하는 Exception이지만 비즈니스 로직에서 처리할 방법이 없을 수 있다
-
SQLException
의 경우 서비스/컨트롤러에서 처리할 방법이 없다. 계속해서 던져진 예외는 DispatcherServlet에서 500코드로 응답하게 된다 - 이렇게 해결 불가능한 예외는 오류 로그를 남기고, 개발자가 빠르게 인지할 수 있도록 알람을 받아 빠르게 해결해야 한다
- 의존관계 문제
- 상위로 던져진 예외에 의존하게 된다.
-
SQLException
의 경우 서비스/컨트롤러에서java.sql.SQLException
에 의존하게 된다
그렇기 때문에 CheckedException을 RuntimeException으로 전환시켜야 한다 (예외 전환)
2. Unchecked Exception
- 컴파일시 체크되지 않고 런타임시 발생하는 exception ->
RuntimeException
상속- 예외처리를 하지 않아도 프로그램은 돌아간다
- 예외로 던질 시 throws 생략 가능 -> 처리되지 않은 예외는 자동으로 던져준다
- 예외발생시 트랜잭션을
rollback
함
ex)NullPointerException
- 런타임 예외는 복구 불가능한 예외를 서비스/컨트롤러에서 신경쓰지 않아도 되며 의존관계 문제도 사라진다
Exception 처리
1. try-catch-finally (복구)
- 현재 로직에서 예외를 처리하는 방법
- try-catch 문에서 여러개를 catch할 경우, 하위에 해당하는 Exception부터 처리해야 한다
- ArithmeticException -> RuntimeException -> Exception 순
public class ExceptionEx1 {
public static void main(String[] args) {
try {
System.out.println(1/0);
} catch (ArithmeticException e) { //ArithmeticException만 실행
System.out.println("0으로 나누지 마세요");
e.printStackTrace();
} catch (RuntimeException e) { //RuntimeException중 ArithmeticException이 아닌것만 실행
System.out.println("연락요망");
e.printStackTrace();
} catch (Exception e) { //RuntimeException 이외의 나머지 Exception이 실행
e.printStackTrace();
}
}
}
-
try문을 실행하다가 예외가 발생했을 때 여러개의 catch문이 존재한다면 위에서부터 instanceof의 결과가 true인 catch문을 찾아내려간다. 찾으면 그 catch문 하나만을 실행하고 try-catch문을 빠져나온다
-
finally
구문은 예외가 발생하던 발생하지 않던 무조건 마지막에 실행된다- 자원 해제 등을 위해 사용한다
public class ExceptionEx2 {
public static void main(String[] args) {
try {
System.out.print(1);
System.out.print(2);
System.out.print(3/0); // exception 발생
System.out.print(4);
} catch (Exception e) {
System.out.print(5);
return; //main method 종료
} finally {
System.out.print(6);
}
System.out.print(7);
}
}
> 1256
2. try-with-resource (복구)
- 자바 7버전부터 추가된 문법
- finally를 사용해서 자원을 수동으로 해제시켜줘야 했지만 이 문법을 사용하면 try구문이 끝나면 자동으로 자원을 해제시켜준다
- try절 시작시 할당한 자원을 구문종료시 해제시킨다
3. throws (회피)
- 현재 로직에서 exception이 발생해도 처리하지 않고 자신을 호출한 상위 메서드로 던지는 방법 (책임회피)
- 상위 메서드에 던지지만 처리되지 않고 메인메서드까지 도달해 처리되지 않는다면 쓰레드가 종료된다
public void doSomething() throws Exception {
//something
}
throw 키워드
- Exception을 발생시키는 키워드
public class ThrowEx {
public static void main(String[] args) {
try {
Exception e = new Exception("고의로 발생");
throw e; // = throw new Exception("고의로 발생");
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
> java.lang.Exception: 고의로 발생
at ch08.ThrowEx.main(asdf.java:7)
고의로 발생
4. 예외 전환 (전환)
- 발생한 예외에 대해 또 다른 예외로 변경하는 것
- 특정한 Exception이 발생하면 새로운 Exception으로 전환하여 구체적으로 예외를 다룰 수 있다
-
CheckedException
을RuntimeException
으로 전환하여 예외를 다룰 수 있도록 전환이 가능하다 -
예외를 전환할 경우 Cause 예외를 반드시 담아서 던져야 한다
- 발생한 예외를 전환시킬 예외의 생성자로 넘겨줘야 정보가 사라지지 않고 상위로 전달된다
try {
...
} catch (xxxException e) {
throw CustomxxxxxException(e);
}
예외처리하는 방법
- 기본적으로 런타임 예외를 사용하자
- 체크예외는 비즈니스 로직상 의도적으로 던지는 예외에만 사용하자 (계좌이체 실패, 포인트 부족 등)
-
catch
문에는 로깅과 복구로직을 추가해라- 로그를 찍을 때는
e.printStackTrace()
를 사용하지 말고 로그 프레임워크를 사용
- 로그를 찍을 때는
- Exception을 무시하거나 다시 던지지 마라
- catch 블럭을 비워두지 마라
- 리소스 정리
- 리소스를 사용하는 구문이라면 반드시 리소스를 해제해야 한다
- 더 자세한 예외
- 메서드 정의시 발생하는 예외를 최대한 자세한 예외를 명세하는 것이 좋습니다
// bad public void exceptableMethod() throw Exception; // good public void execptableMethod() throw NumberFormatException;
- 메시지를 자세하게 적는다
- 예외가 발생하면 메시지를 보고 처리할 수 있도록 가능한 자세하게 적어야 한다