min's devlog

Spring Framework의 전역 에러 처리 본문

til/Server

Spring Framework의 전역 에러 처리

값진 2022. 8. 12. 03:55

Spring Framework의 전역 에러 처리

Spring framework는 전역 에러를 처리하기 위해 아래의 인터페이스를 제공한다.

  제공되는 interface
servlet (webmvc) HandlerExceptionResolver
reacitve (webflux) WebExceptionHandler

Servlet 전역 에러 처리

HandlerExceptionResolver

handlerExceptionResolver는 servlet에서 전역 에러 처리를 하기 위해 제공되는 인터페이스이다.

public interface HandlerExceptionResolver {

    @Nullable
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

아래와 같은 구현체가 제공된다.

구현체 설명
SimpleMappingExceptionResolver 발생한 exception class 이름과 error view 이름을 맵핑해서 사용
boot 에서 기본 설정대상x
DefaultHandlerExceptionResolver 기본적으로 사용되는 exception 들을 공통 처리해주기 위해 제공되는 resolver
ResponseStatusExceptionResolver ResponseStatusException이 throw 되거나 @ResponseStatus를 설정한 지점에서 발생한 에러를 처리
ExceptionHandlerExceptionResolver @Controller 또는 @ControllerAdvice에 선언된 @ExceptionHandler 을 통해 에러를 처리

SimpleMappingExceptionResolver를 제외한 3개의 ExceptionResolver가 Spring boot를 사용하면 기본 등록된다.

DefaultHandlerExceptionResolver는 스프링이 알아서 처리해주는 에러 처리 모음이기 때문에 따로 구현할 선택항목이 아니며, ResponseStatusExceptionResolver를 사용하는 방법은 해당 에러에 종속되거나 어노테이션을 각 메서드나 클래스에 지정해야 하기 때문에 전역으로 사용하기엔 좋은 방법은 아니라고 한다.

 

@ExceptionHandler 에러 처리 만들기

@Controller
public class SimpleController {

    // ...

    @ExceptionHandler
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}

해당 Controller의 메소드에서 발생한 IOException은 위에 @ExceptionHandler로 선언된  handle 메서드를 통해 에러가 처리된다.

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
    // ...
}

위와 같이 @ExceptionHandler에 대상 Exception 목록을 정의하여 처리 대상인 Exception 유형을 좀 더 좁힐 수 있다.

IOException 중 FileSystemException과 RemoteException 에 대해서만 해당 handle 메서드가 동작한다.

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
    // ...
}

위와 같이 대상 Exception을 @ExceptionHandler로 정의하고 일반적인 에러 유형인 Exception이나 또는 상위인 Throwable을 매개변수로 사용할 수도 있다.

@ControllerAdvice와 같이 사용하기

@Controller에 정의된 @ExceptionHandler는 해당 controller에서만 동작한다.

모든 Controller에 대해 발생한 전역 에러에 대해 처리하는 @ExceptionHandler는 @ControllerAdvice와 같이 사용하면 된다.

@Controller
@ControllerAdvice
public class GlobalExceptionController {

    // ...

    @ExceptionHandler
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}

위와 같이 @ControllerAdvice로 선언한 controller내에 정의된 @ExceptionHandler는 모든 controller를 대상으로 동작한다.

controller 대상을 좁혀서 지정하고 싶은 경우 아래처럼 사용하면 된다.

// @RestController를 사용한 모든 controller 대상
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// 해당 패키지 내 모든 controller 대상
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// 해당 클래스 하위로 구현된 controller 대상
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

@ExceptionHandler에서 지원하는 method arguments

@ExceptionHandler를 사용하면서 아래의 매개 변수들을 사용할 수 있다.

매개 변수 설명
Exception type 대상 Exception
HandlerMethod 예외를 발생시킨 controller method
WebRequest, NativeWebRequest Servlet API를 직접 사용하지않고 request parameter와 request, session attribute에 접근
javax.servlet.ServletRequest
javax.servlet.ServletResponse
ServletRequest, HttpServletRequest 또는 스프링의 MultipartRequest, MultipartHttpServletRequest와 같은 유형
javax.servlet.http.HttpSession  
javax.security.Principal  
HttpMethod  
java.util.Locale 현재 요청에 대한 Locale 정보
java.util.TimeZone
java.time.ZoneId
현재 요청에 대한 timezone 정보
java.io.OutputStream
java.io.Writer
 
java.util.Map
org.springframework.ui.Model
org.springframework.ui.ModelMap
error response를 위한 model. empty 상태임
RedirectAttributes  
@SessionAttribute  
@RequestAttribute  

@ExceptionHandler에서 지원하는 return values

반환 값  
@ResponseBody HttpMessageConverter 객체에 의해 변환되어 반환
HttpEntity<B>
ResponseEntity<B>
(header와 body를 포함한) 전체 응답 처리를 HttpMessageConverter 객체를 통해 변환하여 반환
String viewResolver를 통해 처리되는 view 이름
View  
java.util.Map
org.springframework.ui.Model
RequestToViewNameTranslator를 통해 암시적으로 결정된 view 이름을 사용하는 경우 반환 값으로 사용 가능
@ModelAttribute  
ModelAndView object ModelAndView를 반환, response status도 추가 가능
void  
Any other return value 위 열거한 값과 일치하지 않고 BeanUtils#isSimpleProperty에 의해 결정된 simple type이 아닌 경우 model에 추가할 model attribute로 처리됨. simple type인 경우 해결되지 않음

Reactive 전역 에러 처리

WebExceptionHandler

WebExceptionHandler는 reactive에서 에러 처리를 하기 위해 제공되는 인터페이스이다.

public interface WebExceptionHandler {

    Mono<Void> handle(ServerWebExchange exchange, Throwable ex);

}

아래와 같은 구현체가 제공된다.

구현체 설명
ResponseStatusExceptionHandler ResponseStatusException 유형에 대한 예외 처리
WebFluxResponseStatusExceptionHandler @ResponseStatus 가 선언된 경우에 대한 예외처리. ResponseStatusExceptionHandler의 확장

현재는 2가지만 제공하고 있고 따로 구현하여 사용하는 핸들러는 없는 것 같다.

@ExceptionHandler 에러 처리 만들기

Servlet과 마찬가지로 @ControllerAdvice와 @ExceptionHandler를 사용하여 전역 처리가 가능하다.

@Controller
public class SimpleController {

    // ...

    @ExceptionHandler 
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}

또한 매개 변수와 반환 값도 동일하게 사용 가능하다.

다만 request body나 @ModelAttribute에 관련된 매개변수와 반환 값은 사용 불가능하다. 

따라서 Servlet에서 사용 가능한 ModelAndView 같은 반환 값을 쓸 수 없다.

Spring Boot의 에러 처리

Spring Boot는 모든 오류를 적절한 방식으로 처리하여 /error로 매핑하는 전역 오류 페이지 등록을 제공한다.

또한 http 상태와 예외에 대한 적절한 메시지를 json으로 응답하거나 해당 내용을 html 형식으로 렌더링 하는 whitelabel 페이지 뷰를 제공한다.

  제공되는 interface
servlet (webmvc) org.springframework.boot.web.servlet.error.ErrorAttributes
reacitve (webflux) org.springframework.boot.web.reactive.error.ErrorAttributes

두 인터페이스는 거의 유사하다.

// servlet ErrorAttributes
public interface ErrorAttributes {
    Map<String, Object> getErrorAttributes(WebRequest webRequest,
            boolean includeStackTrace);

    Throwable getError(WebRequest webRequest);
}

// reactive ErrorAttributes
public interface ErrorAttributes {

    Map<String, Object> getErrorAttributes(ServerRequest request,
            boolean includeStackTrace);

    Throwable getError(ServerRequest request);

    void storeErrorInformation(Throwable error, ServerWebExchange exchange);

}

이 인터페이스를 통해 아래와 같은 구현체가 제공된다.

  제공되는 구현체 autoConfiguration 설정 구현체 사용 대상
servlet (webmvc) org.springframework.boot.web.servlet.error.DefaultErrorAttributes ErrorMvcAutoConfiguration BasicErrorController
reacitve (webflux) org.springframework.boot.web.reactive.error.DefaultErrorAttributes ErrorWebFluxAutoConfiguration DefaultErrorWebExceptionHandler

Spring boot는 에러가 발생하면 각각 사용 대상에서 ErrorAttributes를 사용하여 에러를 처리하게 된다.

 

DefaultErrorAttributes 사용

스프링이 제공하는 DefaultErrorAttributes는 아래 항목들을 json이나 error view 페이지에 제공한다.

  • timestamp - 에러 발생 시간
  • status - 상태 코드
  • error - 에러 이유
  • exception - 에러 class 이름
  • message - 에러 메시지
  • errors - BindingResult 에러의 경우 ObjectErors
  • trace - exception stack trace
  • path - 에러 발생 url

json으로 응답되는 결과 페이지는 대략 아래와 같은 형태이다.

HTTP/1.1 500 Internal Server Error
{
    "timestamp": 1412685688268,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "com.example.CustomException",
    "message": null,
    "path": "/example"
}

Custom ErrorAttributes 만들기

boot에서는 Servlet과 Reactive를 되도록 유사한 형태로 사용할 수 있도록 ErrorAttributes interface를 제공하기 때문에 아래 예제를 interface만 servlet/reactive 구별하여 상속하면 된다.

@Component
public class TestErrorAttributes implements ErrorAttributes {
    private static final String ERROR_ATTRIBUTE = TestErrorAttributes.class.getName() + ".ERROR";

    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("path", request.path());
        errorAttributes.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
        return errorAttributes;
    }

    @Override
    public Throwable getError(ServerRequest request) {
        return (Throwable) request.attribute(ERROR_ATTRIBUTE)
                .orElseThrow(() -> new IllegalStateException(
                        "Missing exception attribute in ServerWebExchange"));
    }

    // storeErrorInfomation은 reactive에서만 구현하는 method
    @Override
    public void storeErrorInformation(Throwable error, ServerWebExchange exchange) {
        exchange.getAttributes().putIfAbsent(ERROR_ATTRIBUTE, error);
    }
	
}

위와 같이 custom ErrorAttributes를 선언하면 아래처럼 에러 결과가 변경된다.

HTTP/1.1 500 Internal Server Error
{
    "timestamp": 1412685688268,
    "path": "/example"
}

원하는 대로 에러 응답 결과를 바꿀 수 있다.

주의해야 할 점은 errorAttributes에 status란 키 값은 필수로 저장해야 한다.

 

'til > Server' 카테고리의 다른 글

Ajax 서버 응답 처리  (0) 2022.07.05
Ajax, Asynchronous JavaScript and XML  (0) 2022.07.05
[JSP] 로그인 구현  (0) 2022.06.28
[JSP] 게시판 설계  (0) 2022.06.28
[JSP] 메모장  (0) 2022.06.28
Comments