222

我正在使用 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
    }
}

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

例如:

GET /hello?name=Mark&birthDate=X

404 Not Found

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

4

11 回答 11

277

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

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

例子:

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

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

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

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

第二种更简单的方法是 WebApplicationException直接在代码中简单地构造一个实例。只要您不必实现自己的应用程序异常,这种方法就可以工作。

例子:

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

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

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

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

于 2009-03-01T02:40:38.847 回答
70
@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();
}
}

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

于 2011-05-11T07:24:06.850 回答
39

当 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();
    }
}
于 2011-11-28T12:25:57.563 回答
27

您还可以为带有 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());
  }
}

然后像这样使用它:

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

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

于 2009-08-26T05:26:58.777 回答
11

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

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

于 2009-02-28T01:01:49.297 回答
7

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

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

throw new WebApplicationException(Response.Status.BAD_REQUEST);

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

于 2009-03-26T17:11:00.777 回答
4

@QueryParam 文档说

" 带注释的参数、字段或属性的类型 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) 是 List、Set 或 SortedSet,其中 T 满足上述 2、3 或 4。生成的集合是只读的。"

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

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

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

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

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) 构造函数已弃用。如果您使用的是 Java 8,我会使用 Java 8 日期类。否则建议使用 joda 日期时间。

于 2015-04-29T18:36:48.583 回答
1

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

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

于 2015-10-20T20:08:27.040 回答
1

我面临着同样的问题。

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

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

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

覆盖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
}
于 2019-10-08T12:06:33.490 回答
1

方法一:通过扩展 WebApplicationException 类

通过扩展 WebApplicationException 创建新异常

public class RestException extends WebApplicationException {

         private static final long serialVersionUID = 1L;

         public RestException(String message, Status status) {
         super(Response.status(status).entity(message).type(MediaType.TEXT_PLAIN).build());
         }
}

现在在需要时抛出“RestException”。

public static Employee getEmployee(int id) {

         Employee emp = employees.get(id);

         if (emp == null) {
                 throw new RestException("Employee with id " + id + " not exist", Status.NOT_FOUND);
         }
         return emp;
}

您可以在此链接查看完整的应用程序。

方法二:实现 ExceptionMapper

以下映射器处理“DataNotFoundException”类型的异常

@Provider
public class DataNotFoundExceptionMapper implements
        ExceptionMapper<DataNotFoundException> {

    @Override
    public Response toResponse(DataNotFoundException ex) {
        ErrorMessage model = new ErrorMessage(ex.getErrorCode(),
                ex.getMessage());
        return Response.status(Status.NOT_FOUND).entity(model).build();
    }

}

您可以在此链接查看完整的应用程序。

于 2020-01-10T10:25:31.457 回答
0

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

这帮助我构建了响应以强制浏览器登录,请注意对标头的额外修改。这会将状态代码设置为 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"); }
于 2019-04-25T13:10:20.870 回答