Spring 에서Global Error, Exception handling하는 방법
방법 1 – 컨트롤러 수준 @ExceptionHandler
첫 번째 방법은 @Controller 레벨 에서 작동합니다. 예외를 처리하는 메소드를 정의하고 @ExceptionHandler로 어노테이션을 달 것입니다 .
public class FooController{
//...
@ExceptionHandler({ CustomException1.class, CustomException2.class })
public void handleException() {
//
}
}
이 접근 방식에는 큰 단점 이 있습니다. @ExceptionHandler 어노테이션이있는 메소드는 전체 애플리케이션 이 아니라 특정 컨트롤러에 대해서만 활성화 됩니다. 물론, 이것을 모든 컨트롤러에 추가하면 일반적인 예외 처리 메커니즘에 적합하지 않습니다.
모든 컨트롤러가 기본 컨트롤러 클래스를 확장하도록 하여이 제한을 해결할 수 있지만, 어떤 이유로 든 이것이 불가능한 응용 프로그램에는 문제가 될 수 있습니다. 예를 들어, 컨트롤러는 다른 항아리에 있거나 직접 수정할 수 없거나 직접 수정할 수없는 다른 기본 클래스에서 이미 확장 될 수 있습니다.
다음으로 예외 처리 문제를 해결하는 또 다른 방법 (전역적이고 컨트롤러와 같은 기존 아티팩트에 대한 변경 사항은 포함되지 않음)을 살펴 보겠습니다.
방법 2 – HandlerExceptionResolver
두 번째 해결책은 HandlerExceptionResolver 를 정의하는 것입니다. – 응용 프로그램에서 발생한 모든 예외를 해결합니다. 또한 REST API에서 균일 한 예외 처리 메커니즘 을 구현할 수 있습니다.
커스텀 리졸버로 가기 전에 기존 구현을 살펴 보겠습니다.
3.1. ExceptionHandlerExceptionResolver
이 리졸버는 Spring 3.1에서 도입되었으며 기본적으로 DispatcherServlet 에서 활성화됩니다 . 이것은 실제로 이전에 제시된 @ ExceptionHandler 메커니즘이 작동 하는 방식의 핵심 구성 요소입니다 .
3.2. DefaultHandlerExceptionResolver
이 리졸버는 Spring 3.0에서 도입되었으며 기본적으로 DispatcherServlet 에서 활성화되어 있습니다. 표준 Spring 예외를 해당 HTTP 상태 코드, 즉 클라이언트 오류 – 4xx 및 서버 오류 – 5xx 상태 코드 로 해결하는 데 사용됩니다 . 여기에 처리되는 스프링 예외 의 전체 목록 과 상태 코드에 매핑되는 방법이 있습니다.
응답의 상태 코드를 올바르게 설정하지만 한 가지 제한 사항은 응답 본문에 아무것도 설정하지 않는다는 것 입니다. 또한 REST API의 경우 상태 코드는 실제로 클라이언트에게 제공 할 정보가 충분하지 않습니다. 응답에 본문이 있어야 응용 프로그램이 실패에 대한 추가 정보를 제공 할 수 있습니다.
ModelAndView를 통해 뷰 해상도를 구성하고 오류 내용을 렌더링하면이 문제를 해결할 수 있지만 솔루션은 최적이 아닙니다. 이것이 Spring 3.2가 이후 섹션에서 논의 할 더 나은 옵션을 도입 한 이유입니다.
3.3. ResponseStatusExceptionResolver
이 리졸버는 Spring 3.0에서도 도입되었으며 기본적으로 DispatcherServlet 에서 활성화됩니다 . 주된 책임은 사용자 지정 예외에서 사용 가능한 @ResponseStatus 어노테이션 을 사용하여 이러한 예외를 HTTP 상태 코드에 매핑하는 것입니다.
이러한 사용자 정의 예외는 다음과 같습니다.
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class MyResourceNotFoundException extends RuntimeException {
public MyResourceNotFoundException() {
super();
}
public MyResourceNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public MyResourceNotFoundException(String message) {
super(message);
}
public MyResourceNotFoundException(Throwable cause) {
super(cause);
}
}
DefaultHandlerExceptionResolver 와 동일하게 ,이 리졸버는 응답의 본문을 처리하는 방식에 제한이 있습니다. 응답에 상태 코드를 맵핑하지만 본문은 여전히 널입니다.
3.4. SimpleMappingExceptionResolver 및 AnnotationMethodHandlerExceptionResolver
아마도 SimpleMappingExceptionResolver는 꽤 많은 시간 동안 주변되었습니다 - 그것은 기존의 스프링 MVC 모델로 나오고 있습니다 아닌 REST 서비스에 매우 관련 . 기본적으로 예외 클래스 이름을 표시하여 이름을 봅니다.
AnnotationMethodHandlerExceptionResolver는 스루 예외 처리하기 위해 스프링 3.0에 도입 된 @ExceptionHandler의 어노테이션을하지만 의해 사용되지 않습니다 ExceptionHandlerExceptionResolver 봄 3.2로.
3.5. 커스텀 HandlerExceptionResolver
DefaultHandlerExceptionResolver 와 ResponseStatusExceptionResolver 의 조합은 Spring RESTful 서비스에 대한 좋은 오류 처리 메커니즘을 제공하기 위해 먼 길을 간다. 단점은 앞에서 언급했듯이 응답 본문을 제어하지 않습니다 .
이상적으로는 클라이언트가 요청한 형식 ( Accept 헤더 를 통해)에 따라 JSON 또는 XML을 출력 할 수 있기를 원합니다 .
이것만으로도 새로운 사용자 정의 예외 해결 프로그램을 만들 수 있습니다 .
@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
protected ModelAndView doResolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
try {
if (ex instanceof IllegalArgumentException) {
return handleIllegalArgument(
(IllegalArgumentException) ex, response, handler);
}
...
} catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "]
resulted in Exception", handlerException);
}
return null;
}
private ModelAndView
handleIllegalArgument(IllegalArgumentException ex, HttpServletResponse response)
throws IOException {
response.sendError(HttpServletResponse.SC_CONFLICT);
String accept = request.getHeader(HttpHeaders.ACCEPT);
...
return new ModelAndView();
}
}
여기서 주목할 점은 요청 자체에 액세스 할 수 있으므로 클라이언트가 보낸 Accept 헤더 의 값을 고려할 수 있다는 것 입니다.
예를 들어, 클라이언트가 application / json 을 요청 하면 오류 조건의 경우 application / json으로 인코딩 된 응답 본문을 반환해야 합니다.
다른 중요한 구현 세부 사항은 ModelAndView를 반환 한다는 것 입니다. 이것은 응답의 본문 이며 필요한 것을 설정할 수 있습니다.
이 접근법은 Spring REST Service의 오류 처리를위한 일관되고 쉽게 구성 가능한 메커니즘입니다. 그러나 저수준 HtttpServletResponse 와 상호 작용하고 ModelAndView 를 사용하는 이전 MVC 모델에 적합 하므로 여전히 개선의 여지가 있습니다.
방법 3 – @ControllerAdvice
Spring 3.2는 @ControllerAdvice 어노테이션 으로 전역 @ExceptionHandler 를 지원한다 . 이를 통해 이전 MVC 모델 에서 탈피하고 @ExceptionHandler 의 유형 안전성 및 유연성과 함께 ResponseEntity 를 사용하는 메커니즘을 사용할 수 있습니다 .
@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler(value
= { IllegalArgumentException.class, IllegalStateException.class })
protected ResponseEntity<Object> handleConflict(
RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
@ControllerAdvice의 어노테이션이 우리가 할 수 있도록 우리의 복수를 통합, 분산 @ExceptionHandler 하나의 글로벌 오류 처리 구성 요소에 앞에서들 .
실제 메커니즘은 매우 간단하지만 매우 유연합니다. 그것은 우리에게 :
-
응답 본문 및 상태 코드에 대한 모든 권한
-
여러 가지 예외를 동일한 방법으로 매핑하여 함께 처리하고
-
최신 RESTful ResposeEntity
응답 을 잘 활용합니다.
여기서 명심해야 할 것은 @ExceptionHandler 로 선언 된 예외를 메소드의 인수로 사용 된 예외 와 일치 시키는 것 입니다. 이것이 일치하지 않으면 컴파일러는 불평하지 않습니다 – 아무 이유도없고 스프링도 불평하지 않습니다.
그러나 실제로 런타임에 예외가 발생 하면 예외 해결 메커니즘이 실패합니다 .
java.lang.IllegalStateException: No suitable resolver for argument [0] [type=...]
HandlerMethod details: ...
방법 4 – ResponseStatusException (Spring 5 이상)
Spring 5는 ResponseStatusException 클래스를 도입했습니다 . HttpStatus를 제공 하고 선택적으로 이유 와 원인을 제공하는 인스턴스를 만들 수 있습니다 .
@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) {
try {
Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
catch (MyResourceNotFoundException exc) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Foo Not Found", exc);
}
}
ResponseStatusException 을 사용하면 어떤 이점이 있습니까?
- 프로토 타이핑에 탁월 : 기본 솔루션을 매우 빠르게 구현할 수 있습니다
- 하나의 유형, 여러 상태 코드 : 하나의 예외 유형으로 인해 여러 가지 다른 응답이 발생할 수 있습니다. 이것은 @ExceptionHandler에 비해 타이트한 커플 링을 줄입니다.
- 우리는 많은 사용자 정의 예외 클래스를 만들 필요가 없습니다
- 프로그래밍 방식으로 예외를 생성 할 수 있으므로 예외 처리를보다 효과적으로 제어
그리고 트레이드 오프는 어떻습니까?
-
통일 된 예외 처리 방법은 없습니다 . 전역 접근 방식을 제공하는 @ControllerAdvice
와 달리 일부 응용 프로그램 규칙을 적용하기가 더 어렵습니다.
-
코드 복제 : 여러 컨트롤러에서 코드를 복제 할 수 있습니다.
또한 하나의 응용 프로그램 내에서 다양한 접근 방식을 결합 할 수도 있습니다.
예를 들어, @ControllerAdvice를 전역 적으로 구현할 수 있지만 ResponseStatusException 도 로컬로 구현할 수 있습니다. 그러나주의해야합니다. 동일한 예외를 여러 방식으로 처리 할 수 있으면 놀라운 동작이 나타날 수 있습니다. 가능한 한 규칙은 항상 특정 방식으로 하나의 특정 종류의 예외를 처리하는 것입니다.
세부 사항 및 추가 예제 는 ResponseStatusException 학습서를 참조하십시오 .
6. 스프링 보안에서 거부 된 액세스 처리
액세스 거부는 인증 된 사용자가 액세스 할 수있는 권한이없는 리소스에 액세스하려고 할 때 발생합니다.
6.1. MVC – 사용자 정의 오류 페이지
먼저 솔루션의 MVC 스타일을보고 액세스 거부에 대한 오류 페이지를 사용자 정의하는 방법을 살펴 보겠습니다.
XML 구성 :
<http>
<intercept-url pattern="/admin/*" access="hasAnyRole('ROLE_ADMIN')"/>
...
<access-denied-handler error-page="/my-error-page" />
</http>
그리고 Java 구성 :
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/*").hasAnyRole("ROLE_ADMIN")
...
.and()
.exceptionHandling().accessDeniedPage("/my-error-page");
}
충분한 권한이없는 사용자가 리소스에 액세스하려고하면 " / my-error-page " 로 리디렉션됩니다 .
6.2. 사용자 정의 AccessDeniedHandler
다음으로, 커스텀 AccessDeniedHandler 를 작성하는 방법을 보자 :
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle
(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex)
throws IOException, ServletException {
response.sendRedirect("/my-error-page");
}
}
이제 XML 구성을 사용하여 구성 해 보겠습니다 .
<http>
<intercept-url pattern="/admin/*" access="hasAnyRole('ROLE_ADMIN')"/>
...
<access-denied-handler ref="customAccessDeniedHandler" />
</http>
또는 Java 구성을 사용하십시오.
@Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/*").hasAnyRole("ROLE_ADMIN")
...
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
}
우리에 - 방법을 참고 CustomAccessDeniedHandler 우리가 리디렉션하여 원하는 또는 사용자 지정 오류 메시지를 표시, 우리는 응답을 사용자 정의 할 수 있습니다.
6.3. REST 및 메소드 레벨 보안
마지막으로 메소드 수준 보안 @PreAuthorize , @PostAuthorize 및 @Secure Access Denied 를 처리하는 방법을 살펴 보겠습니다 .
물론 앞에서 설명한 전역 예외 처리 메커니즘을 사용하여 AccessDeniedException 도 처리합니다 .
@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler({ AccessDeniedException.class })
public ResponseEntity<Object> handleAccessDeniedException(
Exception ex, WebRequest request) {
return new ResponseEntity<Object>(
"Access denied message here", new HttpHeaders(), HttpStatus.FORBIDDEN);
}
...
}
7. 스프링 부트 지원
Spring Boot는 합리적인 방식으로 오류를 처리하기 위해 ErrorController 구현을 제공합니다 .
간단히 말해서 브라우저에 대한 대체 오류 페이지 (일명 Whitelabel 오류 페이지)와 HTML이 아닌 RESTful 요청에 대한 JSON 응답을 제공합니다.
{
"timestamp": "2019-01-17T16:12:45.977+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Error processing the request!",
"path": "/my-endpoint-with-exceptions"
}
평소와 같이 Spring Boot는 다음 기능을 속성으로 구성 할 수 있습니다.
-
server.error.whitelabel.enabled :
Whitelabel Error Page를 비활성화하고 서블릿 컨테이너를 사용하여 HTML 오류 메시지를 제공하는 데 사용할 수 있습니다 .
-
server.error.include - 스택 트레이스는 : 항상
으로
가치, 그것은 HTML과 JSON의 기본 응답 모두에서 스택 트레이스를 포함
이러한 속성 외에도 Whitelabel 페이지를 재정 의하여 오류에 대한 자체 뷰 리졸버 매핑을 제공 할 수 있습니다 .
컨텍스트에 ErrorAttributes Bean 을 포함시켜 응답에 표시하려는 속성을 사용자 정의 할 수도 있습니다 . Spring Boot에서 제공 하는 DefaultErrorAttributes 클래스를 확장하여 작업을 쉽게 할 수 있습니다.
@Component
public class MyCustomErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(
WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes =
super.getErrorAttributes(webRequest, includeStackTrace);
errorAttributes.put("locale", webRequest.getLocale()
.toString());
errorAttributes.remove("error");
//...
return errorAttributes;
}
}
더 나아가서 애플리케이션이 특정 컨텐츠 유형에 대한 오류를 처리하는 방법을 정의 (또는 대체)하려는 경우 ErrorController Bean을 등록 할 수 있습니다 .
다시, 우리는 Spring Boot가 제공 하는 기본 BasicErrorController를 사용하여 우리를 도울 수 있습니다.
예를 들어, 애플리케이션이 XML 엔드 포인트에서 트리거 된 오류를 처리하는 방법을 사용자 정의하려고한다고 가정하십시오. @RequestMapping을 사용하여 public 메소드를 정의하고 application / xml 매체 유형을 생성한다고 명시하면 됩니다.
@Component
public class MyErrorController extends BasicErrorController {
public MyErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes, new ErrorProperties());
}
@RequestMapping(produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<Map<String, Object>> xmlError(HttpServletRequest request) {
// ...
}
}
8. 결론
이 학습서에서는 스프링에서 REST API에 대한 예외 처리 메커니즘을 구현하는 몇 가지 방법에 대해 설명했습니다. 이전 메커니즘부터 스프링 3.2 지원 및 4.x 및 5.x로 계속 진행하십시오. 제시된 코드는 Github에서 사용할 수 있습니다 . Spring Security 관련 코드의 경우 spring-security-rest 모듈을 확인할 수 있습니다 .
참고
https://www.baeldung.com/exception-handling-for-rest-with-spring
'Spring' 카테고리의 다른 글
RestTemplate에서 List 다루기 (0) | 2020.06.14 |
---|---|
RestTemaplte 사용방법 (0) | 2020.06.13 |
이중화된 시스템 Redis를 통한 Spring security session관리 (0) | 2020.06.13 |
모든 jackson 어노테이션 종류 파악 (1) | 2020.06.11 |
Swagger 를 통한 Spring Rest API 자동화 (0) | 2020.06.10 |