서론
예외가 발생하였을 때 기본적으로 톰캣에서는 다음과 같은 화면을 보여주게 된다.
@RequestMapping("/ex")
public String main() throws Exception {
throw new Exception("예외가 발생하였습니다.");
}
이러한 예외를 유연하게 처리하는 방법에 대해서 알아보도록 하자.
1. Try/Catch
@Controller
public class ExceptionController {
@RequestMapping("/ex")
public String main() throws Exception {
try {
throw new Exception("예외가 발생하였습니다.");
} catch (Exception e) {
return "error";
}
}
}
- 가장 간단한 방법으로, try/catch를 통해 예외 발생 시 error.jsp를 응답해주도록 한다.
2. @ExceptionHandler
@Controller
public class ExceptionController {
@RequestMapping("/ex")
public String main() throws Exception {
try {
throw new Exception("예외가 발생하였습니다.");
} catch (Exception e) {
return "error";
}
}
@RequestMapping("/ex2")
public String main2() throws Exception {
try {
throw new NullPointerException("예외가 발생하였습니다.");
} catch (Exception e) {
return "error";
}
}
}
- 위의 방법처럼 하게 되면, try/catch를 각 메서드별로 사용해주어야 하므로 비효율적인 것을 확인할 수 있다.
@Controller
public class ExceptionController {
@ExceptionHandler(Exception.class)
public String catcher(Exception ex) {
return "error";
}
@ExceptionHandler({NullPointerException.class, FileNotFoundException.class})
public String catcher2(Exception ex) {
return "error";
}
@RequestMapping("/ex")
public String main() throws Exception {
throw new Exception("예외가 발생하였습니다.");
}
@RequestMapping("/ex2")
public String main2() throws Exception {
throw new NullPointerException("예외가 발생하였습니다.");
}
}
- @ExceptionHandler를 사용하면, 예외가 발상하였을 때
자동으로 catcher 메서드가 호출되며 error.jsp를 전달하게 된다. - 만약, NullPointerException, FileNotFoundException를 동일하게 처리해주고 싶다면 위처럼 배열 { } 안에 넣어주고
새로운 메서드를 만들어주면 된다.
(위의 2가지 예외 발생 시 catcher() 메서드 대신 catcher2() 메서드가 예외처리를 수행한다.)
error.jsp (뷰)에 예외에 대한 내용을 추가하고 싶다면, controller처럼 model을 이용하면 된다.
@Controller
public class ExceptionController {
@ExceptionHandler(Exception.class)
public String catcher(Exception ex, Model m) {
//추가
m.addAttribute("method", "ExceptionController : catcher()");
m.addAttribute("ex",ex);
return "error";
}
@ExceptionHandler({NullPointerException.class, FileNotFoundException.class})
public String catcher2(Exception ex, Model m) {
//추가
m.addAttribute("method", "ExceptionController : catcher2()");
m.addAttribute("ex",ex);
return "error";
}
@RequestMapping("/ex")
public String main() throws Exception {
throw new Exception("예외가 발생하였습니다.");
}
@RequestMapping("/ex2")
public String main2() throws Exception {
throw new NullPointerException("예외가 발생하였습니다.");
}
}
Model 추가 (처리한 클래스와 메서드를 확인하기 위한 method, 예외 ex)
<body>
<h1>예외가 발생했습니다.</h1>
발생한 예외 : ${ex}<br>
예외 메시지 : ${ex.message}<br>
처리 메서드 : ${method}<br>
<ol>
<c:forEach items="${ex.stackTrace}" var="i">
<li>${i.toString()}</li>
</c:forEach>
</ol>
</body>
이처럼, 예외 내용이 잘 나오는 것을 확인할 수 있다.
3. @ControllerAdvice
- 위의 @ExceptionHandler를 사용하게 되면 해당 컨트롤러에서 발생한 예외만 처리가 가능하다.
그러면 모든 컨트롤러에 @ExceptionHandler를 통해 예외처리를 해주어야 하지만,
@ControllerAdvice를 사용하면 전역 예외 처리 클래스 작성이 가능해진다.
//@ControllerAdvice("com.springMVC.app") "com.springMVC.app" 패키지에만 적용
@ControllerAdvice //모든 패키지에 적용
public class GlobalCatcher {
@ExceptionHandler(Exception.class)
public String catcher(Exception ex, Model m) {
m.addAttribute("method", "GlobalCatcher : catcher()");
m.addAttribute("ex", ex);
return "error";
}
@ExceptionHandler({NullPointerException.class, FileNotFoundException.class})
public String catcher2(Exception ex, Model m) {
m.addAttribute("method", "GlobalCatcher : catcher2()");
m.addAttribute("ex", ex);
return "error";
}
}
- 이렇게 새로운 클래스 GlobalCatcher를 만들고 @ControllerAdvice를 붙혀주면,
설정한 패키지의 예외를 모두 처리할 수 있게 된다.
@Controller
public class ExceptionController2 {
@RequestMapping("/ex3")
public String main() throws Exception {
throw new FileNotFoundException("예외가 발생하였습니다.");
}
}
- 새로운 클래스 ExceptionController2에는 예외를 발생시키는 main() 메서드 하나만 존재하고,
예외처리 메서드는 존재하지 않는다. - 그렇다면, 예외처리는 GlobalCatcher가 수행하게 될 것이다.
예상대로 GlobalCatcher의 catcher2() 메서드가 처리한 것을 확인할 수 있다.
※ 유의할 점 : ExceptionController의 /ex, /ex2는 여전히 ExceptionController 클래스 내의 예외처리 메서드를 사용한다.
4. @ResponseStatus
- 예외처리를 수행할 때 error.jsp를 전송하게 되는데,
error.jsp가 잘 전송되었다는 의미로 상태코드 200을 전송하게 된다. - 원래의 요청을 제대로 수행하지 못하고 에러 페이지를 전송하였다면 해당 상태코드 200이 올바르다고 할 수 없다.
- 따라서 응답 메시지의 상태 코드를 변경해주어야 한다.
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler({NullPointerException.class, FileNotFoundException.class})
public String catcher2(Exception ex, Model m) {
m.addAttribute("method", "ExceptionController : catcher2()");
m.addAttribute("ex",ex);
return "error";
}
ExceptionController의 catcher2() 메서드에
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) = 500를 추가해보았다.
상태코드 500이 잘 전송된 것을 확인할 수 있다.
5. isErrorPage
<%@ page contentType="text/html;charset=utf-8"%>
<%@ page isErrorPage="true" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>error.jsp</title>
</head>
<body>
<h1>예외가 발생했습니다.</h1>
발생한 예외 : ${pageContext.exception}<br>
예외 메시지 : ${pageContext.exception.message}<br>
처리 메서드 : ${method}<br>
<ol>
<c:forEach items="${pageContext.exception.stackTrace}" var="i">
<li>${i.toString()}</li>
</c:forEach>
</ol>
</body>
</html>
<%@ page isErrorPage="true" %>를 추가하게 되면 ${ex}대신 ${pageContext.exception}
즉, 기본 객체인 exception 객체를 사용할 수 있다.
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler({NullPointerException.class, FileNotFoundException.class})
public String catcher2(Exception ex, Model m) {
m.addAttribute("method", "ExceptionController : catcher2()");
return "error";
}
그렇다면 더 이상 Model에 ex를 추가해줄 필요가 없으므로 이를 지워주도록 하자.
6. error-page (web.xml)
<error-page>
<error-code>500</error-code>
<location>/error500.jsp</location>
</error-page>
<error-page>
<error-code>400</error-code>
<location>/error400.jsp</location>
</error-page>
web.xml에 error-page를 추가해 error-code가 500, 400인 경우 각각 error500.jsp, error400.jsp 로 이동하도록 설정하였다.
error500.jsp, error400.jsp의 위치는 다음과 같다.
webapp/error400.jsp, webapp/error.500.jsp
@ResponseStatus(HttpStatus.BAD_REQUEST) //500 -> 400
class MyException extends RuntimeException{
public MyException(String msg) {
super(msg);
System.out.println(msg);
}
public MyException() {
this("");
}
}
@Controller
public class ExceptionController2 {
@RequestMapping("/ex3")
public String main() throws Exception {
throw new FileNotFoundException("예외가 발생하였습니다.");
}
@RequestMapping("/ex4")
public String main2() throws Exception {
throw new MyException("예외가 발생하였습니다.");
}
}
@ControllerAdvice를 주석으로 지워주고, /ex4에 접속하면
@ResponseStatus를 사용하지 않으면 500, 사용하면 500->400으로 상태코드가 변경되어 400 에러 페이지가 나온다.
결론
여러 예외 처리 방법에 대해서 알아보았다.
try/catch를 사용하는 방법만 알았는데, 여러 방법이 있는 것을 알 수 있었다.
그리고 error페이지를 전송하는것에 성공하여 200의 상태코드를 받게 된다는 것을 생각하지도 못했었는데,
알게 되어서 매우 흥미로웠던 것 같다.
'프로그래밍 > Spring' 카테고리의 다른 글
[Spring] 14. 자바 빈 & 스프링 컨테이너 (Java Bean & Spring Container) (0) | 2023.09.15 |
---|---|
[Spring] 13. 프로젝트 설정 (IntelliJ) (0) | 2023.08.25 |
[Spring] 11.쿠키와 세션 (0) | 2023.08.22 |
[Spring] 10. STS 프로젝트 환경설정 (0) | 2023.08.06 |
[Spring] 9. 리다이렉트(redirect) & 포워드 (forward) (0) | 2023.08.05 |