阅读(4861) (0)

Micronaut 错误处理

2023-02-23 11:25:34 更新

在分布式应用中,有时会发生不好的事情。有一个好的方法来处理错误是很重要的。

状态处理器

@Error 注释支持定义异常类或 HTTP 状态。用@Error 注释的方法必须在用@Controller 注释的类中定义。该注释还支持全局和局部的概念,局部是默认值。

本地错误处理程序仅响应由于路由与同一控制器中的另一个方法匹配而引发的异常。任何抛出的异常都可以调用全局错误处理程序。在解析要执行的处理程序时,总是首先搜索本地错误处理程序。

为异常定义错误处理程序时,您可以将异常实例指定为方法的参数并省略注释的异常属性。

本地错误处理

例如,以下方法在声明控制器的范围内处理来自 Jackson 的 JSON 解析异常:

本地异常处理程序

 Java Groovy  Kotlin 
@Error
public HttpResponse<JsonError> jsonError(HttpRequest request, JsonParseException e) { // (1)
    JsonError error = new JsonError("Invalid JSON: " + e.getMessage()) // (2)
            .link(Link.SELF, Link.of(request.getUri()));

    return HttpResponse.<JsonError>status(HttpStatus.BAD_REQUEST, "Fix Your JSON")
            .body(error); // (3)
}
@Error
HttpResponse<JsonError> jsonError(HttpRequest request, JsonParseException e) { // (1)
    JsonError error = new JsonError("Invalid JSON: " + e.message) // (2)
            .link(Link.SELF, Link.of(request.uri))

    HttpResponse.<JsonError>status(HttpStatus.BAD_REQUEST, "Fix Your JSON")
            .body(error) // (3)
}
@Error
fun jsonError(request: HttpRequest<*>, e: JsonParseException): HttpResponse<JsonError> { // (1)
    val error = JsonError("Invalid JSON: ${e.message}") // (2)
            .link(Link.SELF, Link.of(request.uri))

    return HttpResponse.status<JsonError>(HttpStatus.BAD_REQUEST, "Fix Your JSON")
            .body(error) // (3)
}
  1. 声明了一个显式处理 JsonParseException 的方法

  2. 返回 JsonError 的一个实例。

  3. 返回自定义响应以处理错误

本地状态处理器

 Java Groovy  Kotlin 
@Error(status = HttpStatus.NOT_FOUND)
public HttpResponse<JsonError> notFound(HttpRequest request) { // (1)
    JsonError error = new JsonError("Person Not Found") // (2)
            .link(Link.SELF, Link.of(request.getUri()));

    return HttpResponse.<JsonError>notFound()
            .body(error); // (3)
}
@Error(status = HttpStatus.NOT_FOUND)
HttpResponse<JsonError> notFound(HttpRequest request) { // (1)
    JsonError error = new JsonError("Person Not Found") // (2)
            .link(Link.SELF, Link.of(request.uri))

    HttpResponse.<JsonError>notFound()
            .body(error) // (3)
}
@Error(status = HttpStatus.NOT_FOUND)
fun notFound(request: HttpRequest<*>): HttpResponse<JsonError> { // (1)
    val error = JsonError("Person Not Found") // (2)
            .link(Link.SELF, Link.of(request.uri))

    return HttpResponse.notFound<JsonError>()
            .body(error) // (3)
}
  1. Error 声明要处理的 HttpStatus 错误代码(在本例中为 404)

  2. 为所有 404 响应返回一个 JsonError 实例

  3. 返回 NOT_FOUND 响应

全局错误处理

全局错误处理程序

Java  Groovy  Kotlin 
@Error(global = true) // (1)
public HttpResponse<JsonError> error(HttpRequest request, Throwable e) {
    JsonError error = new JsonError("Bad Things Happened: " + e.getMessage()) // (2)
            .link(Link.SELF, Link.of(request.getUri()));

    return HttpResponse.<JsonError>serverError()
            .body(error); // (3)
}
@Error(global = true) // (1)
HttpResponse<JsonError> error(HttpRequest request, Throwable e) {
    JsonError error = new JsonError("Bad Things Happened: " + e.message) // (2)
            .link(Link.SELF, Link.of(request.uri))

    HttpResponse.<JsonError>serverError()
            .body(error) // (3)
}
@Error(global = true) // (1)
fun error(request: HttpRequest<*>, e: Throwable): HttpResponse<JsonError> {
    val error = JsonError("Bad Things Happened: ${e.message}") // (2)
            .link(Link.SELF, Link.of(request.uri))

    return HttpResponse.serverError<JsonError>()
            .body(error) // (3)
}
  1. @Error 将方法声明为全局错误处理程序

  2. 为所有错误返回一个 JsonError 实例

  3. 返回 INTERNAL_SERVER_ERROR 响应

全局状态处理器

 Java Groovy  Kotlin 
@Error(status = HttpStatus.NOT_FOUND)
public HttpResponse<JsonError> notFound(HttpRequest request) { // (1)
    JsonError error = new JsonError("Person Not Found") // (2)
            .link(Link.SELF, Link.of(request.getUri()));

    return HttpResponse.<JsonError>notFound()
            .body(error); // (3)
}
@Error(status = HttpStatus.NOT_FOUND)
HttpResponse<JsonError> notFound(HttpRequest request) { // (1)
    JsonError error = new JsonError("Person Not Found") // (2)
            .link(Link.SELF, Link.of(request.uri))

    HttpResponse.<JsonError>notFound()
            .body(error) // (3)
}
@Error(status = HttpStatus.NOT_FOUND)
fun notFound(request: HttpRequest<*>): HttpResponse<JsonError> { // (1)
    val error = JsonError("Person Not Found") // (2)
            .link(Link.SELF, Link.of(request.uri))

    return HttpResponse.notFound<JsonError>()
            .body(error) // (3)
}
  1. @Error 声明要处理的 HttpStatus 错误代码(在本例中为 404)

  2. 为所有 404 响应返回一个 JsonError 实例

  3. 返回 NOT_FOUND 响应

关于 @Error 注解的一些注意事项。您不能声明相同的全局 @Error 注释。不能在同一个控制器中声明相同的非全局@Error 注解。如果具有相同参数的 @Error 注解作为全局存在而另一个作为本地存在,则本地注解优先。

异常处理器

或者,您可以实现 ExceptionHandler,这是一个通用挂钩,用于处理在执行 HTTP 请求期间发生的异常。

捕获异常的 @Error 注释优先于捕获相同异常的 ExceptionHandler 的实现。

内置异常处理程序

Micronaut 附带了几个内置的处理程序:

Exception

Handler

javax.validation.ConstraintViolationException

ConstraintExceptionHandler

ContentLengthExceededException

ContentLengthExceededHandler

ConversionErrorException

ConversionErrorHandler

DuplicateRouteException

DuplicateRouteHandler

HttpStatusException

HttpStatusHandler

com.fasterxml.jackson.core.JsonProcessingException

JsonExceptionHandler

java.net.URISyntaxException

URISyntaxHandler

UnsatisfiedArgumentException

UnsatisfiedArgumentHandler

UnsatisfiedRouteException

UnsatisfiedRouteHandler

org.grails.datastore.mapping.validation.ValidationException

ValidationExceptionHandler

自定义异常处理程序

想象一下,当一本书缺货时,您的电子商务应用会抛出 OutOfStockException:

 Java Groovy  Kotlin 
public class OutOfStockException extends RuntimeException {
}
class OutOfStockException extends RuntimeException {
}
class OutOfStockException : RuntimeException()

与 BookController 一起:

 Java Groovy  Kotlin 
@Controller("/books")
public class BookController {

    @Produces(MediaType.TEXT_PLAIN)
    @Get("/stock/{isbn}")
    Integer stock(String isbn) {
        throw new OutOfStockException();
    }
}
@Controller("/books")
class BookController {

    @Produces(MediaType.TEXT_PLAIN)
    @Get("/stock/{isbn}")
    Integer stock(String isbn) {
        throw new OutOfStockException()
    }
}
@Controller("/books")
class BookController {

    @Produces(MediaType.TEXT_PLAIN)
    @Get("/stock/{isbn}")
    internal fun stock(isbn: String): Int? {
        throw OutOfStockException()
    }
}

如果您不处理异常,服务器将返回 500(内部服务器错误)状态代码。

要在抛出 OutOfStockException 时以 400 Bad Request 作为响应,您可以注册一个 ExceptionHandler:

 Java Groovy  Kotlin 
@Produces
@Singleton
@Requires(classes = {OutOfStockException.class, ExceptionHandler.class})
public class OutOfStockExceptionHandler implements ExceptionHandler<OutOfStockException, HttpResponse> {

    private final ErrorResponseProcessor<?> errorResponseProcessor;

    public OutOfStockExceptionHandler(ErrorResponseProcessor<?> errorResponseProcessor) {
        this.errorResponseProcessor = errorResponseProcessor;
    }

    @Override
    public HttpResponse handle(HttpRequest request, OutOfStockException e) {
        return errorResponseProcessor.processResponse(ErrorContext.builder(request)
                .cause(e)
                .errorMessage("No stock available")
                .build(), HttpResponse.badRequest()); // (1)
    }
}
@Produces
@Singleton
@Requires(classes = [OutOfStockException, ExceptionHandler])
class OutOfStockExceptionHandler implements ExceptionHandler<OutOfStockException, HttpResponse> {

    private final ErrorResponseProcessor<?> errorResponseProcessor

    OutOfStockExceptionHandler(ErrorResponseProcessor<?> errorResponseProcessor) {
        this.errorResponseProcessor = errorResponseProcessor
    }

    @Override
    HttpResponse handle(HttpRequest request, OutOfStockException e) {
        errorResponseProcessor.processResponse(ErrorContext.builder(request)
                .cause(e)
                .errorMessage("No stock available")
                .build(), HttpResponse.badRequest()) // (1)
    }
}
@Produces
@Singleton
@Requirements(
    Requires(classes = [OutOfStockException::class, ExceptionHandler::class])
)
class OutOfStockExceptionHandler(private val errorResponseProcessor: ErrorResponseProcessor<Any>) :
    ExceptionHandler<OutOfStockException, HttpResponse<*>> {

    override fun handle(request: HttpRequest<*>, exception: OutOfStockException): HttpResponse<*> {
        return errorResponseProcessor.processResponse(
                ErrorContext.builder(request)
                    .cause(exception)
                    .errorMessage("No stock available")
                    .build(), HttpResponse.badRequest<Any>()) // (1)
    }
}
  1. 默认的 ErrorResponseProcessor 用于创建响应的主体

格式错误

Micronaut 通过类型为 ErrorResponseProcessor 的 bean 生成错误响应主体。

默认响应主体是 vnd.error,但是您可以创建自己的 ErrorResponseProcessor 类型的实现来控制响应。

如果需要自定义响应而不是与错误相关的项目,则需要覆盖处理异常的异常处理程序。