일반적인 요청 흐름
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이다.
차례대로 알아보자.
ExceptionHandlerController
@Controller
public class ExceptionHandlerController {
@GetMapping("simple-null")
public String simpleNullPointerExceptionTest() {
if (true) throw new NullPointerException(); // if 문이 없으면 return 에서 에러 발생이라고 나온다.
return "/";
}
}
핸들러 메소드가 MainController 의 mainRoot()의 핸들러 메소드로 forward 한 것처럼 만든 것
예외 발생으로 인해 nullPointer.html로 forward 된다.
실행 결과 화면

컨트롤러에 추가
@GetMapping("simple-user")
public String simpleUserExceptionTest() throws MemberRegisterException {
if (true) throw new MemberRegisterException("자리가 가득 찼다.");
return "/";
}
Exception을 상속했기 때문에 Checked Exception이고 그래서 throws MemberRegisterException가 붙게 된다.
실행 결과 화면

컨트롤러 안에서 예외 발생 시에는 어떻게 할지에 대해 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이 아닌 안의 메소드에서 호출을 하게 된다.

이것도 바로 위에 한 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에 대해서도 정리해봤다.