기존 WebMvc에서는 ControllerAdvice
와 ExceptionHandler
어노테이션으로
Global한 예외처리를 하였는데
WebFlux에서 Functional Endpoint 로 구현을 하였다면 다른 방식으로 예외처리를 해야합니다.
구글링을 해본 결과 Functional Endpoint 방식같은 경우는 WebExceptionHandler 으로 처리를 해주면 된다고 합니다.
@Component
@Order(-2) // Order(-1) 에 등록된 DefaultErrorWebExceptionHandler 보다 높은 우선순위를 부여하기 위함.
class GlobalExceptionHandler: WebExceptionHandler {
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> =
handle(ex)
.flatMap {
it.writeTo(exchange, HandlerStrategiesResponseContext(HandlerStrategies.withDefaults()))
}.flatMap {
Mono.empty<Void>()
}
fun handle(throwable: Throwable): Mono<ServerResponse> {
return when (throwable) {
is ApiException -> {
createResponse(throwable.exception)
}
else -> {
createResponse(ExceptionMessage.INTERNAL_SERVER_ERROR)
}
}
}
private fun createResponse(exception: ExceptionMessage): Mono<ServerResponse> {
// bodyValue에는 저희가 원하는 Response 형태를 넣어 줍시다.
return ServerResponse.status(exception.statusCode).bodyValue(Response(exception.message, null))
}
}
private class HandlerStrategiesResponseContext(val strategies: HandlerStrategies) : ServerResponse.Context {
override fun messageWriters(): List<HttpMessageWriter<*>> {
return this.strategies.messageWriters()
}
override fun viewResolvers(): List<ViewResolver> {
return this.strategies.viewResolvers()
}
}
class ApiException(
val exception: ExceptionMessage
): RuntimeException()
enum class ExceptionMessage(
val statusCode: HttpStatus,
val message: String
) {
TYPE_EXCEPTION(HttpStatus.BAD_REQUEST, "타입이 잘못되었습니다."),
NOT_FOUND(HttpStatus.NOT_FOUND, "해당 데이터를 못 찾았습니다."),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "알 수 없는 서버 에러입니다.")
}
@Order(-2)
어노테이션을 넣은 이유는
만약 넣지 않게되면 예외가 발생 시 DefaultErrorWebExceptionHandler
가 발생하기때문에 GlobalExceptionHandler
가 작동하지 않는 상황이 발생하기 때문에 우선순위를 -2로 설정을 해줬습니다.
DefaultErrorWebExceptionHandler
의 우선순위는 -1
예외가 발생하면 Throwable의 Exception의 종류에 따라 Response를 만들면 되는데
하지만 저는 Exception의 종류는 한가지로 고정시키고 Exception 프로퍼티로 예외 내용을 가지고 있게끔 만들어줬습니다. (Exception 종류별로 만들어서 따로 처리해줘도 됩니다.)
HandlerStrategiesResponseContext
에서는 생성한 Mono<ServerResponse>
를 실제 Response에 설정을 하여 저희가 원하는 형태로 전달에 성공합니다.
// Post Handler
fun getHotPost(request: ServerRequest): Mono<ServerResponse> {
val postType = request.pathVariable("postType")
// 인기 게시물 타입
if(postType !== "hot")
throw ApiException(ExceptionMessage.TYPE_EXCEPTION)
....
}
RuntimeException
을 상속받은 Exception 종류를 throw 해주면 됩니다.
생각 외로 WebFlux Functional Endpoint 예외처리 방식을 찾는데 시간이 걸렸네요 ㄷㄷ
WebFlux 자료가 생각외로 없더라고요 (기초적인 정보 제외)
그래도 WebFlux 스럽게 예외처리 까지 구현을 해놓아서 해당 프로젝트에서
비즈니스 로직 위주로 집중만 하면 될것같습니다.