Spring 에러 처리

freddie·2021년 4월 25일
0

spring

목록 보기
4/4
post-custom-banner

Spring의 에러처리

ExceptionHandler

Contoller레벨에서 발생하는 에러 처리를 위해 spring에서는 @ExceptionHandler라는 어노테이션을 제공한다.

@RestController
@RequestMapping("/api")
class ApiController() {
    ...

    @ExceptionHandler(Exception::class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    fun handleException(e: Exception): Map<String, String> {
        return mapOf(
            "message" to e.message.orEmpty()
        )
    }
}

이렇게하면 컨트롤러 내부에서 발생하는 에러는 모두 잡을 수 있는데, 단점은 각 컨트롤러마다 선언을 해줘야 한다는 점이다.

ControllerAdvice

이를 위해 전반적인 에러 처리를 위해 Spring에서는 @ControllerAdvice라는 어노테이션을 지원한다.

@RestControllerAdvice
class GlobalControllerAdvice : ResponseEntityExceptionHandler() {
    @ExceptionHandler(Exception::class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    fun handleException(e: Exception): Map<String, String> {
        return mapOf(
            "message" to e.message.orEmpty()
        )
    }
}

이 어노테이션을 범용적으로 컨트롤러 내부에서 발생하는 예외들에 대한 처리를 해준다.
ResponseEntityExceptionHandler에서는 기본적으로 Spring MVC에서 제공되는 에러들에 대한 처리를 할 수 있도록 도와준다.

@ExceptionHandler({
	HttpRequestMethodNotSupportedException.class,
	HttpMediaTypeNotSupportedException.class,
	HttpMediaTypeNotAcceptableException.class,
	MissingPathVariableException.class,
	MissingServletRequestParameterException.class,
	ServletRequestBindingException.class,
	ConversionNotSupportedException.class,
	TypeMismatchException.class,
	HttpMessageNotReadableException.class,
	HttpMessageNotWritableException.class,
	MethodArgumentNotValidException.class,
	MissingServletRequestPartException.class,
	BindException.class,
	NoHandlerFoundException.class,
	AsyncRequestTimeoutException.class
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception

BasicErrorController

Spring security를 적용하다보니 security 에서 발생하는 에러는 @ControllerAdvice에 잡히지 않는 경우가 있었다.

filter에서 발생하는 에러는 spring내부에서 발생하는 에러가 아니라서 문제가 되는 상황이었는데, Spring의 기본적인 에러 처리 방법에 대해 먼저 집고 넘어갈 필요가 있었다.

spring boot프로젝트를 하나 띄워서 없는 주소로 요청을 해보거나, 내부에서 예외를 발생시켜보자.

{
  "timestamp": "2021-04-25T03:08:18.754+00:00",
  "status": 404,
  "error": "Not Found",
  "message": "",
  "path": "/api/asd"
}

위와 같은 응답이 내려오는것을 볼 수 있는데, 이 응답은 어디서 내려주는걸까?

spring boot는 기본적으로 BasicErrorController를 사용해서 모든 에러에 대한 처리를 하고있다.
이 ErrorController는 filter에서 발생하는 에러도 잡아주는데, 에러가 발생하면 서블릿 컨테이너가 캐치해서 에러페이지 경로로 forward해준다고 생각하면 된다.

별도의 ErrorController가 등록되어있지 않으면 ErrorMvcAutoConfiguration를 통해서 관련 설정을 하고있다.

간단한 설정의 변경은 application.properties를 통해서도 변경이 가능하게 제공된다.

server.error.include-stacktrace=on_param
server.error.include-message=always
server.error.path=/custom-error

CustomErrorController

그러나 위에서 제공하는 설정만으로는 부족하여, 에러 응답의 형식을 완전히 변경하는등 좀 더 커스텀한 기능을 넣고싶은 경우가 있을텐데.
이땐 ErrorController를 직접 구현해서 Controller로 등록해주면 된다.

@Controller
@RequestMapping("\${server.error.path:\${error.path:/error}}")
class CustomErrorController(errorAttributes: ErrorAttributes, private val serverProperties: ServerProperties) :
    AbstractErrorController(errorAttributes) {

    @RequestMapping
    fun error(request: HttpServletRequest): ResponseEntity<Map<String, String>> {
        val status = getStatus(request)
        if (status == HttpStatus.NO_CONTENT) {
            return ResponseEntity.noContent().build()
        }

        val attributes = getErrorAttributes(request, ErrorAttributeOptions.defaults())
        val responseBody = mapOf(
            "uri" to attributes["path"]?.toString().orEmpty(),
            "customMessage" to attributes["message"]?.toString().orEmpty()
        )

        return ResponseEntity(responseBody, status)
    }

    override fun getErrorPath(): String {
        return serverProperties.error.path
    }
}

위에서 실행했던 예제와 똑같이 없는 경로로 요청을 날려보면 에러메시지의 형태가 바뀐것을 알 수 있다

{
  "uri": "/api/asd",
  "customMessage": ""
}

참고

(Spring Boot)오류 처리에 대해 | 기록은 재산이다

Error Handling for REST with Spring | Baeldung

스프링부트 기본 에러 페이지 변경하기 - Customize Whitelabel Error Page

profile
하루에 하나씩만 배워보자
post-custom-banner

0개의 댓글