Java JAX-RS/Jersey 如何自定义错误处理?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/583973/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 16:32:06  来源:igfitidea点击:

JAX-RS / Jersey how to customize error handling?

javaresterror-handlingjerseyjax-rs

提问by Mark Renouf

I'm learning JAX-RS (aka, JSR-311) using Jersey. I've successfuly created a Root Resource and am playing around with parameters:

我正在使用 Jersey 学习 JAX-RS(又名 JSR-311)。我已经成功创建了一个根资源并且正在使用参数:

@Path("/hello")
public class HelloWorldResource {

    @GET
    @Produces("text/html")
    public String get(
        @QueryParam("name") String name,
        @QueryParam("birthDate") Date birthDate) {

         // Return a greeting with the name and age
    }
}

This works great, and handles any format in the current locale which is understood by the Date(String) constructor (like YYYY/mm/dd and mm/dd/YYYY). But if I supply a value which is invalid or not understood, I get a 404 response.

这很好用,并且可以处理 Date(String) 构造函数理解的当前语言环境中的任何格式(如 YYYY/mm/dd 和 mm/dd/YYYY)。但是如果我提供一个无效或不被理解的值,我会收到 404 响应。

For example:

例如:

GET /hello?name=Mark&birthDate=X

404 Not Found

How can I customize this behavior? Maybe a different response code (probably "400 Bad Request")? What about logging an error? Maybe add a description of the problem ("bad date format") in a custom header to aid troubleshooting? Or return a whole Error response with details, along with a 5xx status code?

如何自定义此行为?也许不同的响应代码(可能是“400 Bad Request”)?记录错误怎么样?也许在自定义标题中添加问题描述(“错误的日期格式”)以帮助进行故障排除?或者返回包含详细信息的整个错误响应以及 5xx 状态代码?

采纳答案by Steven Levine

There are several approaches to customize the error handling behavior with JAX-RS. Here are three of the easier ways.

有多种方法可以使用 JAX-RS 自定义错误处理行为。以下是三种更简单的方法。

The first approach is to create an Exception class that extends WebApplicationException.

第一种方法是创建一个扩展 WebApplicationException 的 Exception 类。

Example:

例子:

public class NotAuthorizedException extends WebApplicationException {
     public NotAuthorizedException(String message) {
         super(Response.status(Response.Status.UNAUTHORIZED)
             .entity(message).type(MediaType.TEXT_PLAIN).build());
     }
}

And to throw this newly create Exception you simply:

要抛出这个新创建的异常,你只需:

@Path("accounts/{accountId}/")
    public Item getItem(@PathParam("accountId") String accountId) {
       // An unauthorized user tries to enter
       throw new NotAuthorizedException("You Don't Have Permission");
}

Notice, you don't need to declare the exception in a throws clause because WebApplicationException is a runtime Exception. This will return a 401 response to the client.

请注意,您不需要在 throws 子句中声明异常,因为 WebApplicationException 是运行时异常。这将向客户端返回 401 响应。

The second and easier approach is to simply construct an instance of the WebApplicationExceptiondirectly in your code. This approach works as long as you don't have to implement your own application Exceptions.

第二种更简单的方法是WebApplicationException直接在代码中构建 的实例 。只要您不必实现自己的应用程序异常,这种方法就有效。

Example:

例子:

@Path("accounts/{accountId}/")
public Item getItem(@PathParam("accountId") String accountId) {
   // An unauthorized user tries to enter
   throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}

This code too returns a 401 to the client.

此代码也会向客户端返回 401。

Of course, this is just a simple example. You can make the Exception much more complex if necessary, and you can generate what ever http response code you need to.

当然,这只是一个简单的例子。如有必要,您可以使异常更加复杂,并且您可以生成您需要的任何 http 响应代码。

One other approach is to wrap an existing Exception, perhaps an ObjectNotFoundExceptionwith an small wrapper class that implements the ExceptionMapperinterface annotated with a @Providerannotation. This tells the JAX-RS runtime, that if the wrapped Exception is raised, return the response code defined in the ExceptionMapper.

另一种方法是包装一个现有的异常,可能是一个ObjectNotFoundException带有一个小包装类的包装类,它实现了ExceptionMapper@Provider注释注释的接口。这告诉 JAX-RS 运行时,如果引发了包装的异常,则返回ExceptionMapper.

回答by StaxMan

One obvious solution: take in a String, convert to Date yourself. That way you can define format you want, catch exceptions and either re-throw or customize error being sent. For parsing, SimpleDateFormat should work fine.

一个明显的解决方案:接收一个字符串,自己转换为日期。这样你就可以定义你想要的格式,捕捉异常并重新抛出或自定义正在发送的错误。对于解析,SimpleDateFormat 应该可以正常工作。

I am sure there are ways to hook handlers for data types too, but perhaps little bit of simple code is all you need in this case.

我相信也有一些方法可以为数据类型挂钩处理程序,但在这种情况下,您可能只需要一点简单的代码。

回答by dshaw

I too like StaxManwould probably implement that QueryParam as a String, then handle the conversion, rethrowing as necessary.

我也喜欢StaxMan可能会将该 QueryParam 实现为字符串,然后处理转换,必要时重新抛出。

If the locale specific behavior is the desired and expected behavior, you would use the following to return the 400 BAD REQUEST error:

如果特定于语言环境的行为是所需和预期的行为,您将使用以下内容返回 400 BAD REQUEST 错误:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

throw new WebApplicationException(Response.Status.BAD_REQUEST);

See the JavaDoc for javax.ws.rs.core.Response.Statusfor more options.

有关更多选项,请参阅javax.ws.rs.core.Response.Status的 JavaDoc 。

回答by Charlie Brooking

You could also write a reusable class for QueryParam-annotated variables

您还可以为 QueryParam 注释的变量编写一个可重用的类

public class DateParam {
  private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

  private Calendar date;

  public DateParam(String in) throws WebApplicationException {
    try {
      date = Calendar.getInstance();
      date.setTime(format.parse(in));
    }
    catch (ParseException exception) {
      throw new WebApplicationException(400);
    }
  }
  public Calendar getDate() {
    return date;
  }
  public String format() {
    return format.format(value.getTime());
  }
}

then use it like this:

然后像这样使用它:

private @QueryParam("from") DateParam startDateParam;
private @QueryParam("to") DateParam endDateParam;
// ...
startDateParam.getDate();

Although the error handling is trivial in this case (throwing a 400 response), using this class allows you to factor-out parameter handling in general which might include logging etc.

尽管在这种情况下错误处理是微不足道的(抛出 400 响应),但使用此类允许您排除一般的参数处理,其中可能包括日志记录等。

回答by Arnav

@Provider
public class BadURIExceptionMapper implements ExceptionMapper<NotFoundException> {

public Response toResponse(NotFoundException exception){

    return Response.status(Response.Status.NOT_FOUND).
    entity(new ErrorResponse(exception.getClass().toString(),
                exception.getMessage()) ).
    build();
}
}

Create above class. This will handle 404 (NotFoundException) and here in toResponse method you can give your custom response. Similarly there are ParamException etc. which you would need to map to provide customized responses.

创建上面的类。这将处理 404 (NotFoundException),在 toResponse 方法中,您可以提供自定义响应。同样,您需要映射 ParamException 等以提供自定义响应。

回答by Jan Kronquist

Jersey throws an com.sun.jersey.api.ParamException when it fails to unmarshall the parameters so one solution is to create an ExceptionMapper that handles these types of exceptions:

Jersey 在解组参数失败时抛出 com.sun.jersey.api.ParamException ,因此一种解决方案是创建一个 ExceptionMapper 来处理这些类型的异常:

@Provider
public class ParamExceptionMapper implements ExceptionMapper<ParamException> {
    @Override
    public Response toResponse(ParamException exception) {
        return Response.status(Status.BAD_REQUEST).entity(exception.getParameterName() + " incorrect type").build();
    }
}

回答by Srikanth

@QueryParam documentation says

@QueryParam 文档说

" The type T of the annotated parameter, field or property must either:

1) Be a primitive type
2) Have a constructor that accepts a single String argument
3) Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String))
4) Have a registered implementation of javax.ws.rs.ext.ParamConverterProvider JAX-RS extension SPI that returns a javax.ws.rs.ext.ParamConverter instance capable of a "from string" conversion for the type.
5) Be List, Set or SortedSet, where T satisfies 2, 3 or 4 above. The resulting collection is read-only. "

" 带注释的参数、字段或属性的类型 T 必须:

1) 是一个原始类型
2) 有一个接受单个 String 参数的构造函数
3) 有一个名为 valueOf 或 fromString 的静态方法,它接受单个 String 参数(例如,参见 Integer.valueOf(String))
4) 有一个javax.ws.rs.ext.ParamConverterProvider JAX-RS 扩展 SPI 的注册实现,它返回一个能够对类型进行“从字符串”转换的 javax.ws.rs.ext.ParamConverter 实例。
5) Be List, Set or SortedSet,其中T满足以上2、3或4。生成的集合是只读的。”

If you want to control what response goes to user when query parameter in String form can't be converted to your type T, you can throw WebApplicationException. Dropwizard comes with following *Param classes you can use for your needs.

如果您想控制当 String 形式的查询参数无法转换为您的类型 T 时对用户的响应,您可以抛出 WebApplicationException。Dropwizard 带有以下 *Param 类,您可以根据需要使用。

BooleanParam, DateTimeParam, IntParam, LongParam, LocalDateParam, NonEmptyStringParam, UUIDParam. See https://github.com/dropwizard/dropwizard/tree/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/params

BooleanParam、DateTimeParam、IntParam、LongParam、LocalDateParam、NonEmptyStringParam、UUIDParam。见https://github.com/dropwizard/dropwizard/tree/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/params

If you need Joda DateTime, just use Dropwizard DateTimeParam.

如果您需要 Joda DateTime,只需使用 Dropwizard DateTimeParam

If the above list does not suit your needs, define your own by extending AbstractParam. Override parse method. If you need control over error response body, override error method.

如果以上列表不适合您的需要,请通过扩展 AbstractParam 定义您自己的列表。覆盖解析方法。如果您需要控制错误响应正文,请覆盖错误方法。

Good article from Coda Hale on this is at http://codahale.com/what-makes-jersey-interesting-parameter-classes/

来自 Coda Hale 的好文章在http://codahale.com/what-makes-jersey-interesting-parameter-classes/

import io.dropwizard.jersey.params.AbstractParam;

import java.util.Date;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

public class DateParam extends AbstractParam<Date> {

    public DateParam(String input) {
        super(input);
    }

    @Override
    protected Date parse(String input) throws Exception {
        return new Date(input);
    }

    @Override
    protected Response error(String input, Exception e) {
        // customize response body if you like here by specifying entity
        return Response.status(Status.BAD_REQUEST).build();
    }
}

Date(String arg) constructor is deprecated. I would use Java 8 date classes if you are on Java 8. Otherwise joda date time is recommended.

不推荐使用 Date(String arg) 构造函数。如果您使用的是 Java 8,我会使用 Java 8 日期类。否则建议使用 joda 日期时间。

回答by ACV

This is the correct behavior actually. Jersey will try to find a handler for your input and will try to construct an object from the provided input. In this case it will try to create a new Date object with the value X provided to the constructor. Since this is an invalid date, by convention Jersey will return 404.

这实际上是正确的行为。Jersey 将尝试为您的输入找到一个处理程序,并尝试从提供的输入构建一个对象。在这种情况下,它将尝试使用提供给构造函数的值 X 创建一个新的 Date 对象。由于这是一个无效日期,按照惯例,Jersey 将返回 404。

What you can do is rewrite and put birth date as a String, then try to parse and if you don't get what you want, you're free to throw any exception you want by any of the exception mapping mechanisms (there are several).

你可以做的是重写并将出生日期作为一个字符串,然后尝试解析,如果你没有得到你想要的,你可以通过任何异常映射机制随意抛出任何你想要的异常(有几个)。

回答by Omnibyte

Just as an extension to @Steven Lavine answer in case you want to open the browser login window. I found it hard to properly return the Response (MDN HTTP Authentication) from the Filter in case that the user wasn't authenticated yet

就像@Steven Lavine 回答的扩展一样,以防您想打开浏览器登录窗口。我发现很难从过滤器正确返回响应(MDN HTTP 身份验证),以防用户尚未通过身份验证

This helped me to build the Response to force browser login, note the additional modification of the headers. This will set the status code to 401 and set the header that causes the browser to open the username/password dialog.

这帮助我构建了强制浏览器登录的响应,注意标题的额外修改。这会将状态代码设置为 401 并设置导致浏览器打开用户名/密码对话框的标题。

// The extended Exception class
public class NotLoggedInException extends WebApplicationException {
  public NotLoggedInException(String message) {
    super(Response.status(Response.Status.UNAUTHORIZED)
      .entity(message)
      .type(MediaType.TEXT_PLAIN)
      .header("WWW-Authenticate", "Basic realm=SecuredApp").build()); 
  }
}

// Usage in the Filter
if(headers.get("Authorization") == null) { throw new NotLoggedInException("Not logged in"); }

回答by suraj.tripathi

I was facing the same issue.

我面临着同样的问题。

I wanted to catch all the errors at a central place and transform them.

我想在一个中心位置捕获所有错误并对其进行转换。

Following is the code for how I handled it.

以下是我如何处理它的代码。

Create the following class which implements ExceptionMapperand add @Providerannotation on this class. This will handle all the exceptions.

创建以下类,该类在此类上实现ExceptionMapper并添加@Provider注释。这将处理所有异常。

Override toResponsemethod and return the Response object populated with customised data.

覆盖toResponse方法并返回填充了自定义数据的 Response 对象。

//ExceptionMapperProvider.java
/**
 * exception thrown by restful endpoints will be caught and transformed here
 * so that client gets a proper error message
 */
@Provider
public class ExceptionMapperProvider implements ExceptionMapper<Throwable> {
    private final ErrorTransformer errorTransformer = new ErrorTransformer();

    public ExceptionMapperProvider() {

    }

    @Override
    public Response toResponse(Throwable throwable) {
        //transforming the error using the custom logic of ErrorTransformer 
        final ServiceError errorResponse = errorTransformer.getErrorResponse(throwable);
        final ResponseBuilder responseBuilder = Response.status(errorResponse.getStatus());

        if (errorResponse.getBody().isPresent()) {
            responseBuilder.type(MediaType.APPLICATION_JSON_TYPE);
            responseBuilder.entity(errorResponse.getBody().get());
        }

        for (Map.Entry<String, String> header : errorResponse.getHeaders().entrySet()) {
            responseBuilder.header(header.getKey(), header.getValue());
        }

        return responseBuilder.build();
    }
}

// ErrorTransformer.java
/**
 * Error transformation logic
 */
public class ErrorTransformer {
    public ServiceError getErrorResponse(Throwable throwable) {
        ServiceError serviceError = new ServiceError();
        //add you logic here
        serviceError.setStatus(getStatus(throwable));
        serviceError.setBody(getBody(throwable));
        serviceError.setHeaders(getHeaders(throwable));

    }
    private String getStatus(Throwable throwable) {
        //your logic
    }
    private Optional<String> getBody(Throwable throwable) {
        //your logic
    }
    private Map<String, String> getHeaders(Throwable throwable) {
        //your logic
    }
}

//ServiceError.java
/**
 * error data holder
 */
public class ServiceError {
    private int status;
    private Map<String, String> headers;
    private Optional<String> body;
    //setters and getters
}