예외 계층
- Object : 예외도 객체. 모든 객체의 최상위 부모는 Object이므로 예외의 최상위 부모도 Object
- Throwable : 최상위 예외
- Error : 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구 불가능한 시스템 예외
- 개발자는 이 예외를 잡으려고 해서는 안됨
- 상위 예외를 catch로 잡으면 그 하위 예외까지 함께 잡음
- 애플리케이션 로직에서는 Throwable 예외를 잡으면 Error 예외도 함께 잡을 수 있기 때문에 해당 예외를 잡으면 안됨
- 언체크 예외
- Exception : 체크 예외
- 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외
- Exception과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외
- 단 RuntimeException은 예외
- RuntimeException : 언체크 예외 = 런타임 예외
- 컴파일러가 체크하지 않는 언체크 예외
- 그 자식 예외 또한 모두 언체크 예외
- 런타임 예외라고도 함
예외 기본 규칙
- 예외는 잡아서 처리하거나 던져야 함
- 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리됨
- Exception을 catch로 잡으면 그 하위 예외들도 모두 잡을 수 있음
- Exception을 throws로 던지면 그 하위 예외들도 모두 던질 수 있음
예외를 처리하지 못하고 계속 던지는 경우
- 자바 main() 쓰레드의 경우 예외 로그를 출력하면서 시스템이 종료됨
- 웹 애플리케이션의 경우 WAS가 해당 예외를 받아서 처리하는데, 주로 개발자가 지정한 오류 페이지를 보여줌
체크 예외(Exception과 그 하위 예외)
public class CheckedTest {
...
static class Controller{
Service service = new Service();
public void request() throws SQLException, ConnectException {
service.logic();
}
}
static class Service{
Repository repository = new Repository();
NetworkClient networkClient = new NetworkClient();
public void logic() throws SQLException, ConnectException {
repository.call();
networkClient.call();
}
}
static class NetworkClient{
public void call() throws ConnectException {
throw new ConnectException("연결 실패");
}
}
static class Repository{
public void call() throws SQLException {
throw new SQLException("예외 발생");
}
}
}
- Exception과 그 하위 예외(RuntimeException 제외)는 모두 컴파일러가 체크하는 체크 예외
- 체크 예외는 잡아서 처리하거나 밖으로 던지도록 선언 필수. 그렇지 않으면 컴파일 오류 발생
장단점
- 장점 : 개발자가 실수로 예외를 누락하지 않을 수 있음
- 단점 : 모든 체크 예외를 반드시 잡거나 던지도록 해야 하기 때문에 catch, throw 등 선언 필수
체크 예외 문제점
1. 복구 불가능한 예외
- SQLException을 예를 들면 문법 또는 DB 자체에 문제가 발생(서버 다운 등)하여 대부분 복구가 불가능
- 특히나 서비스나 컨트롤러는 이런 문제를 해결할 수 없음
- 따라서 오류를 빠르게 인지하여 서블릿 필터, 스프링 인터셉터, 스프링의 ControllerAdvice를 사용하여 공통으로 해결해야 함
2. 의존 관계에 대한 문제
- 체크 예외이기 때문에 컨트롤러나 서비스 입장에서는 본인이 처리할 수 없어도 어쩔 수 없이 예외를 던져야 함
- 예외를 던지는 부분을 코드에 선언하게 되면 서비스나 컨트롤러에서 java.sql.SQLException을 의존하게 됨
- 향후 기술 변경 시 SQLException에 의존하던 모든 서비스, 컨트롤러의 코드를 JPAException에 의존하도록 고쳐야 함
언체크 예외(RuntimeException과 그 하위 예외)
public class UncheckedAppTest {
...
static class Controller{
Service service = new Service();
public void request() {
service.logic();
}
}
static class Service{
Repository repository = new Repository();
NetworkClient networkClient = new NetworkClient();
public void logic() {
repository.call();
networkClient.call();
}
}
static class NetworkClient{
public void call() {
throw new RuntimeConnectException("연결 실패");
}
}
static class Repository{
public void call() {
try {
runSQL();
} catch (SQLException e) {
throw new RuntimeSQLException(e);
}
}
public void runSQL() throws SQLException {
throw new SQLException("예외 발생");
}
}
static class RuntimeConnectException extends RuntimeException{
public RuntimeConnectException(String message) {
super(message);
}
}
static class RuntimeSQLException extends RuntimeException{
public RuntimeSQLException(Throwable cause) {
super(cause);
}
}
}
- RuntimeException과 그 하위 예외는 컴파일러가 체크하지 않은 언체크 예외
- 예외를 던지는 throws를 선언하지 않고 생략할 시 자동으로 예외를 던져줌
장단점
- 장점 : 체크하고 싶지 않은 언체크 예외 무시 가능. 즉, throws 미선언 가능
- 단점 : 컴파일러가 체크해주지 않기 때문에 개발자가 예외를 누락할 수 있음
언체크 예외 활용
@Slf4j
public class UncheckedAppTest {
@Test
void Unchecked(){
Controller controller = new Controller();
Assertions.assertThatThrownBy(() -> controller.request())
.isInstanceOf(Exception.class);
}
@Test
void printEx(){
Controller controller = new Controller();
try {
controller.request();
} catch(Exception e){
log.info("ex", e);
}
}
static class Controller{
Service service = new Service();
public void request() {
service.logic();
}
}
static class Service{
Repository repository = new Repository();
NetworkClient networkClient = new NetworkClient();
public void logic() {
repository.call();
networkClient.call();
}
}
static class NetworkClient{
public void call() {
throw new RuntimeConnectException("연결 실패");
}
}
static class Repository{
public void call() {
try {
runSQL();
} catch (SQLException e) {
// 체크 예외를 언체크 예외로 전환
throw new RuntimeSQLException(e);
}
}
public void runSQL() throws SQLException {
throw new SQLException("ex");
}
}
static class RuntimeConnectException extends RuntimeException{
public RuntimeConnectException(String message) {
super(message);
}
}
static class RuntimeSQLException extends RuntimeException{
public RuntimeSQLException(Throwable cause) {
super(cause);
}
}
}
- 중간에 기술이 변경되어도 해당 예외를 사용하지 않는 컨트롤러, 서비스에서는 코드를 변경하지 않아도 됨
- 구현 기술이 변경되는 경우, 예외를 공통으로 처리하는 곳에서 예외에 따른 다른 처리가 필요할 수 있음
- 하지만 공통으로 처리하는 곳만 변경하면 되기 때문에 변경의 영향 범위는 최소화
- throws 생략 가능
'Java > Spring Basic' 카테고리의 다른 글
프로토타입 스코프 (0) | 2024.09.01 |
---|---|
빈 생명주기 콜백 (1) | 2024.09.01 |
의존관계 자동 주입 (0) | 2024.08.29 |
@ComponentScan (0) | 2024.08.21 |
@Configuration (0) | 2024.08.20 |