Exception Handling in Spring MVC
Spring MVC는 예외처리를 위한 몇가지 훌륭한 접근법을 제공해주지만 Spring MVC를 가르칠때 학생들이 종종 헷갈려하거나 불편해한다는 것을 알았다.
이 글에서 이를 위해 사용가능한 다양한 옵션을 보여줄 것이다. 우리의 목표는 가능한한 컨트롤러 메소드에서 명시적으로 예외처리를 하지 않는 것이다. 이들의 횡단관심사cross-cutting concern는 전용코드에서 별도로 처리하는 더 나은 방식을 제공해준다.
3가지 옵션이 있다: 예외별, 컨트롤러별, 전역별 per exception, per controller or globally.
이 글에서 다루어진 예제 어플리케이션은 다음의 주소에서 받아볼 수 있다
http://github.com/paulc4/mvc-exceptions.
아래의 Sample Application 섹션에 자세한 설명을 해두었다..
알림: 데모 어플리케이션은 2014년 10월에 새롭게 다시 업데이트되어 스프링 부트 1.1.8에서 사용가능하며, 사용하거나 이해하기 더 쉽게 바뀌었다.
Using HTTP Status Codes
HTTP 상태코드 사용하기보통 웹요청 처리시 발생하는 미처리 예외unhandled exception는 서버가 HTTP 500 응답을 리턴한다. 그러나 당신이 작성한 커스텀 예외를 (HTTP명세서에 의해 정의된 모든 HTTP 상태코드를 지원하는) @ResponseStatus
어노테이션과 함께 사용할 수 있다. 어노테이션된 예외annotated exception가 컨트롤러 메소드에서 발생할 때, 그리고 다른 곳에서 처리되지 않을때, 이는 자동적으로 특정한 상태코드를 가지고 리턴되는 적절한 HTTP응답을 발생할 것이다.
예를 들면, 여기 Order가 빠진 경우의 예외exception를 보자:
그리고 그것을 사용하고 있는 컨트롤러:
만일 이 메소드에 유효하지않은 Order ID가 들어오면 익숙한 HTTP 404 응답이 리턴되어질 것이다.
Controller Based Exception Handling
컨트롤러 기반 예외처리Using @ExceptionHandler
@ExceptionHandler 사용하기당신은 부가적인 (@ExceptionHandler
) 메소드를 어느 컨트롤러에나 추가하여 같은 컨트롤러의 요청처리request handling (@RequestMapping
) 메소드에 의해 발생하는 예외처리들을 구체화할 수 있다. 이러한 메소드들은 다음의 일들을 할 수 있다:
@ResponseStatus
어노테이션 없이 예외를 처리한다 (보통 당신이 작성하지않은 선정의된 예외들)- 사용자를 특정한 에러페이지로 리다이렉트한다
- 완전히 컨스텀 에러 응답을 만든다
아래의 컨트롤러는 이 3가지 옵션을 보여준다:
이 메소드들중 아무거나, 당신이 추가적인 처리를 위해 고를 수 있다. 가장 일반적인 예제는 예외를 로그하는 것이다.
처리Handler 메소드는 유연한 특징을 가지고 있어 당신이 HttpServletRequest
, HttpServletResponse
, HttpSession
그리고/또는 Principl
. 와 같은 명확히 서블릿 관련 객체들을 패스할 수 있다. 중요한 알림: Model 은 @ExceptionHandler
메소드의 파라메터가 될 수 없다. 대신, 위의 handleError()
에 의해 보여진.ModelAndView
를 사용하여 메소드안에 하나의 모델을 설정할 수 있다.
Exceptions and Views
예외와 뷰예외를 모델에 추가할 때 조심해야할 점은, 당신의 사용자들은 구체적인 자바 예외나 stack-trace가 포함된 웹페이지를 보고 싶지않아 한다는 것이다. 하지만 페이지 소스안에 코멘트로서 구체적인 예외 상태를 넣어 당신을 서포트하는 사람들을 도와주려는 것은 유용할 수 있다. 만일 JSP를 사용한다면 당신은 아래와 같이 예외 메세지나 (숨겨진 <div>
를 사용하여) stack-trace를 출력하는 등등을 할 수 있을 것이다.
타임리프Thymeleaf에서 이와 같은 일을 하려면 support.html를 보자
예제 어플리케이션에선 다음과 같은 결과를 볼 수 있다.
Global Exception Handling
전역 예외 처리Using @ControllerAdvice Classes
@ControllerAdvice 클래스 사용하기컨트롤러 어드바이스는 당신에게 똑같은 예외처리 기술을 사용하지만, 개별 컨트롤러가 아니라 전체 어플리케이션에 적용할 수 있게 만들어 준다. 이들을 어노테이션 기반 인터셉터annotation driven interceptor로 이해하면 될 것이다.
@ControllerAdvice
어노테이션을 가지는 클래스는 컨트롤러 어드바이스controller-advice가 되며 3가지 타입을 메소드를 지원할 수 있다:
@ExceptionHandler
으로 어노테이션된 예외처리 메소드- @ModelAttribute으로 어노테이션된 (추가적인 데이터를 모델에 추가하기 위한) 모델 향상Model enhancement 메소드. [Note] 이들 속성들은 예외처리 뷰에서 사용할 수 없다.
@InitBinder
로 어노테이션된 (폼처리를 설정하는데 사용되는) 바인더 초기화Binder initialization 메소드
우리는 여기서 예외처리만 다룰것이므로 @ControllerAdvice
메소드에 대한 자세한 사항은 온라인 메뉴얼을 보자.
위에서 본 어느 예외처리도 컨트롤러-어드바이스 클래스에서 정의할 수 있다 - 그러나 이제 이들은 이제 모든 컨트롤러에서 발생하는 예외에 적용될 것이다. 아래 간단한 예제를 보자:
어떠한 예외에 처리되는 기본 처리자가 필요하면, 다음과 같이 약간만 손보면 된다. 어노테이션된 예제는 프레임워크에 의해 처리된다는 것을 명심하자:
Going Deeper
더 자세히 들어가보기HandlerExceptionResolver
HandlerExceptionResolver
를 구현한 DispatcherServlet
의 application context에서 선언된 모든 스프링빈은 MVC 시스템에서 올라오는 모든 예외를 처리하고 인터셉트하는데 사용되어지며 컨트롤러에 의해 처리되지않는다. 인터페이스틑 아래와 같다:
handler
는 예외가 발생한 컨트롤러를 참조한다. (@Controller
인스턴스들은 스프링 MVC가 지원하는 핸들러의 하나의 타입일 뿐이라는 것을 기억하자. 예를 들면, HttpInvokerExporter
와 WebFlow Executor 또한 핸들러의 타입들이다.
이 뒷단에서 MVC는 세가지 resolver를 기본으로 생성한다. 이 3가지 리졸버들은 위에 논의돈 행동들을 구현한 것이다:
ExceptionHandlerExceptionResolver
는 핸들러(컨트롤러)와 컨트롤러-어드바이스들상의 적절한@ExceptionHandler
메소드를 위한 uncaught exception에 맞닿아 매치된다.- matches uncaught exceptions against for
suitable@ExceptionHandler
methods on both the handler (controller) and on any controller-advices. ResponseStatusExceptionResolver
는 (섹션1에서 설명한)@ResponseStatus
에 의해 어노테이션 된 uncaught exception들을 찾는다.DefaultHandlerExceptionResolver
는 표준 스프링 예외를 변환하고 그들을 HTTP상태코드로 변환한다. (스프링MVC에서 내부적으 동작하는 부분에 대해서 언급하지는 않겠다)
이들은 서로 순서에 따라 연쇄작동하고 처리한다. (내부적으로 스프링은 이를 담당하는 빈들 생성하는데 - HandlerExceptionResolverComposite이 이를 담당한다)
resolveException
의 메소드 시그니쳐는 Model
을 포함하지않는다는 것을 상기하자. 아래에 그 이유가 있다
@Configuration
@EnableWebMvc // Optionally setup Spring MVC defaults if you aren’t doing so elsewhere
public class MvcConfiguration extends WebMvcConfigurerAdapter {
@Bean(name=“simpleMappingExceptionResolver”)
public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r =
new SimpleMappingExceptionResolver();
}
public class MyMappingExceptionResolver extends SimpleMappingExceptionResolver {
public MyMappingExceptionResolver() {
// Enable logging by providing the name of the logger to use
setWarnLogCategory(MyMappingExceptionResolver.class.getName());
}
}
public class ErrorInfo {
public final String url;
public final String ex;
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MyBadDataException.class)
@ResponseBody ErrorInfo handleBadRequest(HttpServletRequest req, Exception ex) {
return new ErrorInfo(req.getRequestURL(), ex);
}
```
What to Use When?
언제 무엇을 써야하나?보통 스프링은 당신에게 선택할 수 있게 제공하는 것을 선호한다. 그래서 당신이 해야하는게 뭘까? 여기 몇가지 중요한 룰이 있다. 하지만 XML설정이나 어노테이션을 선호한다면 그 역시 상관없다.
- 당신이 작성한 예외들에
@ResponseStatus
를 추가하는 것을 고려하라. @ControllerAdvice
클래스에@ExceptionHandler
메소드를 구현하거나 SimpleMappingExceptionResolver의 인스턴스를 사용하는 모든 종류의 예외들에 대해 아마 당신의 어플리케이션 설정에 이미SimpleMappingExceptionResolver
를 이미 사용하고 있다면, 여기에 새로운 예외클래스를 추가하는게@ControllerAdvice
를 구현하는 것보다 더 쉬울 것이다.- 컨트롤러 특정 예외 처리를 하려면 당신의 컨트롤러에
@ExceptionHandler
메소드를 추가하자. - Warning: 같은 어플리케이션에 이들 옵션을 너무 많이 혼용하여 사용하지 않아야한다. 같은 예외같 하나 이상의 방식으로 처리되어질 수 있고, 이경우 원치않는 행동을 얻을 수 있다. 컨트롤러에서
@ExceptionHandler
메소드는 항상@ControllerAdvice
인스턴스의 메소드들 전에 선택되어진다. 무슨 컨트롤러 어드바이스가 먼저 처리되는지 정의하지 않는다.
예제 어플리케이션 Sample Application
예제 어플리케이션은 github에서 받을 수 있다.
스프링 부트와 타임리프를 사용하는 간단한 웹어플리케이션이다.
이 어플리케이션은 2014년 10월에 더 이해하기 쉽게 개정되었다. 그 기반은 동일하다. 스프링부트 1.1.8과 스프링 4.1을 사용하지만 스프링 3.x에서 또한 동작가능하다.
이 데모는 클라우드 파운드리의 http://mvc-exceptions-v2.cfapps.io/에서 동작하고 있다.
About the Demo
데모에 대해어플리케이션은 서로 다른 예외처리 기술을 쓰는 5개의 데모페이지를 가지고 있다:
- 그 자신의 예외처리를 위한
@ExceptionHandler
메소드를 가진 하나의 컨트롤러 - 글로벌 컨트롤러 어드바이저에 의해 처리되는 예외를 뿌리는 하나의 컨트롤러
SimpleMappingExceptionResolver
를 사용하여 예외처리- 3번과 동일하지만 비교를 위해
SimpleMappingExceptionResolver
를 disabled 함 - 어떻게 스프링 부트가 에러페이지를 만드는지 보여줌
홈 웹페이지는 index.html 이며:
- 각 데모페이지로의 링크
- 스프링부트에 관심있는 사람을 위해 스프링 부트 종단의 링크
각각 데모페이지는 몇개의 링크를 가지고 있으며 모두 예외를 발생시킨다. 당신 브라우저 백버튼을 사용하여 각 데모페이지로 되돌아 올수 있다.
이 데모를 내장 톰켓 컨테이너에서 자바 어플리케이션으로 실행할 수 있게 만든 스프링 부트에 감사한다. 이 어플리케이션을 싱핼하려면 다음중 하나의 명령어를 사용하면 된다:
mvn exec:java
mvn spring-boot:run
기본 홈페이지 URL은 http://localhost:8080.
Spring Boot and Error Handling
스프링 부트와 에러처리스프링 부트 는 스프링 프로젝트를 최소한의 설정으로 돌릴 수 있게 만들어준다. 스프링부트는 클래스 패스의 키 클래스과 패키지들을 찾아 자동으로 민감한 기본값들을 생성한다. 예를 들면 당신이 서블릿 환경을 사용중이라면 스프링 MVC를 가장 일반적으로 많이 사용하는 뷰-리졸버view-resolvers, 핸들러 매핑handler mappings 등등을 설정해준다. 만일 JSP나 타임리프 파일이 있으면 그에 맞는 해당 뷰 기술을 자동으로 설정한다.
스프링 MVC는 기본적으로 제공하는 에러페이지가 없다. 기본 에러페이지를 설정하는 가장 흔한 방법은 언제나 SimpleMappingExceptionResolver
를 가지는 것이다. (스프링 버전1 이후로) 그러나 스프링 부트는 또한 에러 처리fallback error-handling 페이지를 제공하고 있다.
시작시 스프링 부트는 /error
를 위한 매핑을 찾는다. 명명법에 의해 /error
로 끝나는 URL은 같은 이름의 논리적 뷰와 매핑된다: error
. 데모 어플리케이션에서, 이 뷰는 타임리프 템플릿의 error.html로 매핑된다. (만일 JSP를 사용하고 있다면 InternalResourceViewResolver
가 설정되면서 error.jsp
로 매핑될 것이다)
/error
와 매핑되는 뷰가 없다면, 스프링 부트는 “Whitelabel Error Page” 라 불리는 그 자신의 에러페이지를 정의한다 (HTTP상태 정보와 uncaught exception로 부터의 메세지와 같은 어떠한 에러 디테일 가지는 최소한의 페이지). 만일 error.html
템플릿을 이를테면, error2.html
로 이름을 바꾸고 재시작하면 이것이 사용되어지는 것을 확인할 수 있다.
defaultErrorView()
로 불리는 @Bean
메소드를 자바 설정으로 정의함으로서, 당신은 자신만의 에러 View
인스턴스를 리턴할 수 있다. (더 자세한 정보는 스프링 부트의ErrorMvcAutoConfiguration
클래스를 보자)
기본 에러 뷰를 설정하기 위해 이미 SimpleMappingExceptionResolver
를 사용중이라면? 간단히 defaultErrorView
에 스프링부트에서 사용하는 같은 뷰: error
를 정의해주면 된다. 또는 application.properties파일에 error.whitelabel.enabled
를 false
로 설정하여 스프링 부트의 기본 에러페이지를 disabled하면 된다. 이경우 당신의 컨테이너의 기본 에러페이지가 사용될 것이다.
Main의 생성자에서 스프링 부트 프로퍼티를 설정하는 예제링크
이 데모에서 SimpleMappingExceptionResolver
의 defaultErrorView
프로퍼티는 의도적으로 error
이 아니라 defaultErrorPage
로 설정되어 당신은 핸들러가 에러페이지를 생성할때나 스프링 부트가 응답할때 볼 수 있을 것이다. 보통은 둘다 error
로 설정되어 있다.
또한 이 데모 어플리케이션에서 stack-trce를 HTML소스에 숨겨둔 서포트 준비가 된support-ready 에러페이지를 만드는 법을 확인할 수 있다. (코멘트로서). 이상적으로 이러한 정보는 로그로 부터 얻어야 하지만 실제 삶은 언제나 이상적이지 않는다. 어쨋든 이 페이지에서 보려주려고 하는 것은 어떻게 존재하는 에러처리 메소드인 handleError
가 그 자신에 추가적인 정보를 제공하기 위해 ModelAndView
를 만드는 가 이다. 다음의 사항도 확인해보자:
* ExceptionHandlingController.handleError()
on github
* GlobalControllerExceptionHandler.handleError()
on github
출처: http://springboot.tistory.com/25 [스프링부트는 사랑입니다]
'Programming > Java & JSP & Spring' 카테고리의 다른 글
[JSON] KEY 값 추출해서 사용하기 (0) | 2017.08.17 |
---|---|
[Java] String, Date 타입 간의 변환 (0) | 2017.08.17 |
[SpringBoot] 프로퍼티 이용하기 (0) | 2017.08.08 |
[SpringBoot] 스타터 의존성 사용하기 (0) | 2017.08.08 |
[JPA] 특정 칼럼을 제외하고 INSERT, UPDATE하는 방법 (0) | 2017.08.01 |