
실행 중 에러가 터졌을 때 그냥 톰캣 기본 에러 페이지가 뜨면:
그래서 스프링 MVC는 예외를 깔끔하게 모아서 처리하는 기능을 제공한다.
그중 핵심 키워드가:
HandlerExceptionResolver@ExceptionHandler@ControllerAdviceDispatcherServlet이 요청을 처리하다가 예외가 발생하면:
그냥 터뜨리는 게 아니라
등록된 HandlerExceptionResolver 들에게
“이 예외 좀 처리해줘” 하고 넘긴다.
대표적인 구현체들:
SimpleMappingExceptionResolverDefaultHandlerExceptionResolverResponseStatusExceptionResolver@ResponseStatus 달린 예외를 보고 HTTP 상태코드 + 메시지 응답ExceptionHandlerExceptionResolver@ExceptionHandler 메서드를 찾아서 호출해 줌오늘 내용은 이중에서
@ExceptionHandler + @ControllerAdvice를 중점적으로 보는 느낌이야.
버튼 두 개로 각각 다른 요청을 보낸다.
<button onclick="location.href='controller-null'">NullPointerException 테스트</button>
<button onclick="location.href='controller-user'">사용자 정의 Exception 테스트</button>
컨트롤러에서 일부러 예외를 일으킴:
@GetMapping("controller-null")
public String nullPointerExceptionTest() {
String str = null;
System.out.println(str.charAt(0)); // 여기서 NPE 발생
return "/";
}
@GetMapping("controller-user")
public String userExceptionTest() throws MemberRegistException {
boolean check = true;
if(check) {
throw new MemberRegistException("당신 같은 사람은 회원으로 받을 수 없습니다!");
}
return "/";
}
지금은 예외 처리를 안 했으니까:
NullPointerException → 기본 에러 페이지MemberRegistException → 기본 에러 페이지즉, 그냥 “스프링 부트 기본 에러 화면”이 튀어나온다.
이제 같은 컨트롤러 클래스 안에 예외 처리 메서드를 추가한다.
@ExceptionHandler(NullPointerException.class)
public String nullPointerExceptionHandler(NullPointerException exception) {
System.out.println("controller 레벨의 exception 처리");
return "error/nullPointer";
}
에러용 뷰:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>nullPointer</title>
</head>
<body>
<h1>널 위하여 NullPointerException 발생함!</h1>
</body>
</html>
사용자 정의 예외도 비슷하게:
@ExceptionHandler(MemberRegistException.class)
public String userExceptionHandler(Model model, MemberRegistException exception) {
System.out.println("controller 레벨의 exception 처리");
model.addAttribute("exception", exception);
return "error/memberRegist";
}
뷰:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>memberRegist</title>
</head>
<body>
<h1 th:text="${ exception.message }"></h1>
</body>
</html>
위의 동작 흐름을 파악해보자.
controller-null 호출 → 메소드 안에서 NPE 발생@ExceptionHandler(NullPointerException.class) 이 있으므로error/nullPointer.html로 포워드controller-user 호출 → MemberRegistException 발생@ExceptionHandler(MemberRegistException.class) 메서드가 실행error/memberRegist 뷰로 이동정리하면:
@ExceptionHandler(예외타입.class)이번엔 다른 컨트롤러를 새로 만든다.
버튼:
<button onclick="location.href='other-controller-null'">
NullPointerException 테스트
</button>
<button onclick="location.href='other-controller-user'">
사용자 정의 Exception 테스트
</button>
컨트롤러:
@GetMapping("other-controller-null")
public String otherNullPointerExceptionTest() {
String str = null;
System.out.println(str.charAt(0));
return "/";
}
@GetMapping("other-controller-user")
public String otherUserExceptionTest() throws MemberRegistException {
boolean check = true;
if(check) {
throw new MemberRegistException("당신 같은 사람은 회원으로 받을 수 없습니다!");
}
return "/";
}
이 컨트롤러에는 @ExceptionHandler가 없으니까:
@ExceptionHandler는 “다른 컨트롤러”에 붙어 있어서 적용 안 됨즉, 컨트롤러 안에 있는 @ExceptionHandler는 그 컨트롤러 전용임.
컨트롤러가 여러 개고, 각각에 전부 @ExceptionHandler를 쓰면 코드 중복도 많고 관리가 힘들다.
→ @ExceptionHandler는 지역 에러처리 핸들러이기 때문
그래서 전역 예외 처리용 클래스를 하나 만든다.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NullPointerException.class)
public String nullPointerExceptionHandler(NullPointerException exception) {
System.out.println("Global 레벨의 exception 처리");
return "error/nullPointer";
}
@ExceptionHandler(MemberRegistException.class)
public String userExceptionHandler(Model model, MemberRegistException exception) {
System.out.println("Global 레벨의 exception 처리");
model.addAttribute("exception", exception);
return "error/memberRegist";
}
}
설명 :
NullPointerException이 발생하든@ExceptionHandler가 없다면 → GlobalExceptionHandler의 메소드가 불린다. 이 메소드 안에서 적용된 @ExceptionHandler는 전역적으로 사용 가능하다.MemberRegistException도 동일동작 우선순위:
@ExceptionHandler@ControllerAdvice가 붙은 전역 핸들러이번엔 다른 종류의 예외:
버튼:
<button onclick="location.href='other-controller-array'">ArrayException 테스트</button>
컨트롤러:
@GetMapping("other-controller-array")
public String otherArrayExceptionTest() {
double[] array = new double[0];
System.out.println(array[0]); // ArrayIndexOutOfBoundsException 발생
return "/";
}
전역 핸들러에 “모든 예외의 상위 타입”인 Exception을 처리하는 메서드를 추가:
@ExceptionHandler(Exception.class)
public String defaultExceptionHandler(Exception exception) {
return "error/default";
}
뷰:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>default</title>
</head>
<body>
<h1>뭔가 에러가 발생함</h1>
</body>
</html>
이제 어떤 예외가 터져도:
NullPointerException, MemberRegistException)가 있는지 확인Exception.class용 핸들러가 “기본 처리기” 역할을 해줌→ “모든 예외에 대한 fallback 처리기” 느낌으로 쓰면 된다.
ResponseStatusExceptionResolver는 @ResponseStatus가 붙은 예외 클래스를 보고 응답을 만들어준다. 예외 클래스 위에 HTTP 상태코드를 직접 박아버리는 방식이다.
예시:
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "리소스를 찾을 수 없습니다.")
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
컨트롤러:
@Controller
public class ResourceController {
@GetMapping("/resource/{id}")
public String getResource(@PathVariable("id") int id) {
// 리소스 부재 시 예외 발생
if (id <= 0) {
throw new ResourceNotFoundException("유효하지 않은 리소스 ID");
}
return "/";
}
}
요청 /resource/0 처럼 잘못된 id를 보내면:
ResourceNotFoundException이 던져지고@ResponseStatus(HttpStatus.NOT_FOUND) 때문에"리소스를 찾을 수 없습니다."주로 REST API에서:
예외 타입 → 에러 페이지 뷰 이름을 properties처럼 일괄 매핑하는 방식.
설정 코드:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties exceptionMappings = new Properties();
// 예외 타입별로 뷰 이름 매핑
exceptionMappings.put("java.lang.Exception", "error");
exceptionMappings.put("java.lang.RuntimeException", "runtimeError");
resolver.setExceptionMappings(exceptionMappings);
resolver.setDefaultErrorView("defaultError"); // 기본 에러 뷰
resolver.setExceptionAttribute("exception"); // 뷰에 전달할 예외 객체 이름
return resolver;
}
}
동작 방식:
RuntimeException이 발생하면 → runtimeError.html로 포워드Exception) → error.htmldefaultError.html${exception}으로 예외 객체 접근 가능요약:
@ExceptionHandler처럼 코드 안에 메서드를 만들기 싫고 설정만으로 간단하게 처리하고 싶은 경우에 적합@ExceptionHandler@ControllerAdvice@ResponseStatusSimpleMappingExceptionResolver