Spring - Exception Handler 에 대해

제훈·2024년 8월 14일

Spring

목록 보기
12/18

Exception Handler

일반적인 요청 흐름

WAS(톰캣) -> 필터 -> 서블릿(디스패처 서블릿) -> 인터셉터 -> 컨트롤러

이 때 컨트롤러에서 에러가 발생했는데도 별도의 예외 처리를 하지 않으면 WAS까지 에러가 전달된다.

그러면 WAS는 애플리케이션에서 처리를 못하는 예와라 exception이 올라왔다고 판단을 하고, 대응 작업을 진행한다.

컨트롤러(예외발생) -> 인터셉터 -> 서블릿(디스패처 서블릿) -> 필터 -> WAS(톰캣)

WAS는 스프링 부트가 등록한 에러 설정(/error)에 맞게 요청을 전달하게 된다.

 WAS(톰캣) -> 필터 -> 서블릿(디스패처 서블릿) -> 인터셉터 -> 컨트롤러(BasicErrorController)

기본적인 에러 처리 방식은 결국 에러 컨트롤러를 한번 더 호출하는 것


예외처리하는 방식은 굉~장히 많지만 ExceptionResolver@ExceptionHandler 어노테이션을 활용해서 에러 처리를 하는 방식에 대해서 알아보자.

기본 세팅

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 align="center">Exception Handler 사용하기</h1>
    <h3>SimpleMappingExceptionResolver를 이용한 방식</h3>
    <button onclick="location.href='simple-null'">NullPointerException 테스트</button>
    <button onclick="location.href='simple-user'">사용자 정의 Exception 테스트</button>

    <h3>@ExceptionHandler 어노테이션을 이용한 방식</h3>
    <button onclick="location.href='annotation-null'">NullPointerException 테스트</button>
    <button onclick="location.href='annotation-user'">사용자 정의 Exception 테스트</button>
</body>
</html>

나머지 html도 body 부 속에서만 작성했기 때문에 body부만 작성하고 생략하겠다.

nullPointer.html

<body>
    <h1 align="center">NullPointerException 발생함</h1>
</body>

예외 발생 시 예외 메시지를 출력해보기 위해서 작성한 html 파일이다.

memberRegister.html

<body>
    <h1 align="center">더 이상 가입이 불가합니다.</h1>
    <h1 align="center" th:text="${exceptionMessage.message}"></h1>
</body>

핸들링 돼 넘어온 페이지에서는 setExceptionAttribute로 작성한 키 값으로 예외 타입과 메시지를 추출할 수 있다.


예외에 따른 설정한 페이지말고는 default 페이지로 적용할 것이다.

default.html

<body>
<!--    <h1 align="center" th:text="${exceptionMessage}"></h1>-->
    <h1 align="center" th:text="${userException.message}"></h1>

</body>

기존에는 exceptionMessage를 사용했으나, MemberRegisterException과 관련해서 묶기 위해서 한 줄 더 추가했으니 더 보자.


이제 컨트롤러 클래스다.

MainController

@Controller
public class MainController {

    @RequestMapping("/main")
    public void main() {}

    @RequestMapping("/")
    public String mainRoot() {
        return "main";
    }
}

이건 스프링을 실행하고 localhost:8080 이나 localhost:8080/main 이나 같은 기존 main.html 화면이 뜨게 된다.


이제 설정 파일로 지정한 RootConfiguration 클래스다.

RootConfiguration

@Configuration
public class RootConfiguration {
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

        Properties properties = new Properties();
        properties.setProperty("java.lang.NullPointerException", "error/nullPointer");
        properties.setProperty("MemberRegisterException", "error/memberRegister");

        /* 설명. 1. 예외에 따른 페이지 설정한 것 적용 */
        exceptionResolver.setExceptionMappings(properties);

        /* 설명. 2. 그 외 나머지 예외에 대한 default 페이지 적용 */
        exceptionResolver.setDefaultErrorView("error/default");

        /* 설명. 3. 예외 메시지를 뽑기 위한 Attribute key 값 ( 화면에서 예외 메시지 확인 시 사용할 키 값) */
        exceptionResolver.setExceptionAttribute("exceptionMessage");

         return exceptionResolver;
    }
}

예외에 따른 페이지 -> NullPointerException이 뜰 때, MemberRegisterException이 뜰 때
2가지 경우에 적용된다.

나머지는 default...


그럼 간단하게 MemberRegisterException은 어떻게 이루어져 있을까?

MemberRegisterException

public class MemberRegisterException extends Exception {
    public MemberRegisterException(String message) {
        super(message);
    }
}

Exception 을 상속하고 있는 것을 기억하고 있자.


다음은 이제 위에서 나온 ExceptionHandlerController이다.

차례대로 알아보자.

1. SimpleMappingExceptionResolver

NullPointerException

ExceptionHandlerController

@Controller
public class ExceptionHandlerController {
    @GetMapping("simple-null")
    public String simpleNullPointerExceptionTest() {
        if (true) throw new NullPointerException(); // if 문이 없으면 return 에서 에러 발생이라고 나온다.
        return "/";
    }
}
  • 핸들러 메소드가 MainControllermainRoot()의 핸들러 메소드로 forward 한 것처럼 만든 것

    • 하지만 예외를 발생시키기 때문에 도달하지는 않는다.
  • 예외 발생으로 인해 nullPointer.htmlforward 된다.


실행 결과 화면


MemberRegisterException

컨트롤러에 추가

    @GetMapping("simple-user")
    public String simpleUserExceptionTest() throws MemberRegisterException {
        if (true) throw new MemberRegisterException("자리가 가득 찼다.");
        
        return "/";
    }

Exception을 상속했기 때문에 Checked Exception이고 그래서 throws MemberRegisterException가 붙게 된다.


실행 결과 화면


@ExceptionHandler 어노테이션을 이용한 방식

NullPointerException

컨트롤러 안에서 예외 발생 시에는 어떻게 할지에 대해 RootConfiguration 이라고 설정 파일이 있긴 하지만 그것보다 우선적으로 적용할 수 있다.

죽, 이후 핸들러 메소드들은 이 클래스만 해당되는 지역범위 예외처리 설정을 한다.

그것도 한 번 같이 보려고 한다.

컨트롤러에 추가

    @GetMapping("annotation-null")
    public String annotationNullPoinerExceptionTest() {
        String str = null;
        System.out.println("str.charAt(0) :" + str.charAt(0));

        return "/";
    }

    @ExceptionHandler(NullPointerException.class)
    public String nullPointerExceptionHandler() {
        System.out.println("이 Controller에서 NullPointerException 발생 시 이 메소드로 오는지 확인");

        return "error/nullPointer";
    }

str = null 이기 때문에 charAt(0)NullPointerException 이 터지게 되고, RootConfiguration이 아닌 안의 메소드에서 호출을 하게 된다.


MemberRegisterException

이것도 바로 위에 한 NullPointerException의 예제와 같다.

컨트롤러에 추가

    @GetMapping("annotation-user")
    public String annotationUserExceptionTest() throws MemberRegisterException {
        if(true) throw new MemberRegisterException("더 이상 받을 수 없다고 다시 알림.");

        return "/";
    }

    @ExceptionHandler(MemberRegisterException.class)
    public String UserExceptionHandler(Model model, MemberRegisterException exception) {
        model.addAttribute("userException", exception);

        return "error/default";
    }

캡처 화면

exception이 터지면 똑같이 다른 핸들러가 먼저 호출되게 된다.

간단하게 Exception Handler에 대해서도 정리해봤다.

profile
백엔드 개발자 꿈나무

0개의 댓글