Webflux에서 Annotated Controllers 패턴에서는 주로 MVC와 비슷하게 @ControllerAdvice 어노테이션과 @ExceptionHandler 어노테이션을 이용하는데요.
Functional Endpoints 패턴에서는 주로 ErrorWebExceptionHandler을 활용합니다. 해당 인터페이스를 구현한 AbstractErrorWebExceptionHandler를 사용해볼거에요.
AbstractErrorWebExceptionHandler
해당 생성자가 Deprecated 되었습니다. (Deprecated since 2.4.0 for removal in 2.6.0 in favor)
@Deprecated
public AbstractErrorWebExceptionHandler(ErrorAttributes errorAttributes,
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
ApplicationContext applicationContext) {
this(errorAttributes, (Resources) resourceProperties, applicationContext);
}
많은 예제들이 위의 생성자를 이용하였는데요.
2.4.0부터는 새로 추가된 생성자를 이용하는 것이 좋겠습니다.
public AbstractErrorWebExceptionHandler(ErrorAttributes errorAttributes, Resources resources,
ApplicationContext applicationContext) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
Assert.notNull(resources, "Resources must not be null");
Assert.notNull(applicationContext, "ApplicationContext must not be null");
this.errorAttributes = errorAttributes;
this.resources = resources;
this.applicationContext = applicationContext;
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
}
이제 해당 클래스를 활용하여 Global ExceptionHandler를 만들어볼게요.
예제 코드는 코틀린으로 작성하였습니다.
ExceptionHandler
@Component
@Order(-2)
class ExceptionHandler(
errorAttributes: ErrorAttributes,
resources: WebProperties.Resources,
applicationContext: ApplicationContext,
serverCodecConfigurer: ServerCodecConfigurer,
) : AbstractErrorWebExceptionHandler(
errorAttributes, resources, applicationContext
) {
init {
super.setMessageReaders(serverCodecConfigurer.readers)
super.setMessageWriters(serverCodecConfigurer.writers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes?): RouterFunction<ServerResponse> =
RouterFunctions.route(RequestPredicates.all(), this::handleError)
fun handleError(request: ServerRequest): Mono<ServerResponse> {
return when (val throwable = super.getError(request)) {
is ServiceException -> {
val serviceMessage = throwable.serviceMessage
ServerResponse.status(serviceMessage.status)
.bodyValue(
ErrorResponse(
name = serviceMessage.name,
message = serviceMessage.message,
)
)
}
else -> {
ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
.bodyValue(
ErrorResponse(
name = throwable.javaClass.simpleName,
message = throwable.message,
)
)
}
}
}
}
ServiceException과 ServiceMessage는 커스텀으로 사용하는 예외처리 클래스입니다.
아래 샘플 코드가 있으며 자신이 사용하고 있는 Exception으로 대체해주시면 됩니다.
특정 Exception에 대해 별도로 처리하고 싶으면 when 문 내에 is *Exception 추가하여 별도로 코딩하시면 됩니다.
개인적으로 Exception 종류가 너무 많아진다하면 ServerResponse build 하는 부분을 모두 별도 파일로 분리하고 있어요.
ServiceException
class ServiceException(val serviceMessage: ServiceMessage) : RuntimeException()
ServiceMessage
enum class ServiceMessage(
val status: HttpStatus,
val message: String,
) {
NOT_FOUND_BOARD(HttpStatus.NOT_FOUND, "Board not found."),
;
fun exception(): ServiceException {
return ServiceException(this)
}
fun <T> error(): Mono<T> {
return Mono.error(this.exception())
}
}
'Backend > Spring' 카테고리의 다른 글
Spring Boot Reactive Redis Transaction (0) | 2022.02.15 |
---|---|
Spring Boot Gradle Multi Modules 초간단 (0) | 2022.02.11 |
Spring Boot ReactiveRedisTemplate<String, Integer> (0) | 2021.08.14 |
Spring Boot MongoDB multi-document transactions (0) | 2021.08.14 |
Spring Webflux Annotated Controller @ClientIp (0) | 2021.07.31 |