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

Java JAX-RS/Jersey如何定制错误处理?,java,rest,error-handling,jersey,jax-rs,Java,Rest,Error Handling,Jersey,Jax Rs,我正在使用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

我正在使用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
    }
}
这非常有效,可以处理日期(字符串)构造函数(如YYYY/mm/dd和mm/dd/YYYY)可以理解的当前区域设置中的任何格式。但是如果我提供了一个无效或不可理解的值,我会得到404响应

例如:

GET/hello?name=Mark&birthDate=X
404找不到

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

一个显而易见的解决方案:输入字符串,自己转换为日期。通过这种方式,您可以定义所需的格式,捕获异常并重新抛出或自定义发送的错误。 对于解析,SimpleDataFormat应该可以正常工作


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

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

第一种方法是创建一个扩展WebApplicationException的异常类

例如:

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 WebApplicationException(Response.Status.UNAUTHORIZED);
}
要抛出这个新创建的异常,只需执行以下操作:

@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
的实例。只要您不必实现自己的应用程序异常,这种方法就可以工作

例如:

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 WebApplicationException(Response.Status.UNAUTHORIZED);
}
此代码也会将401返回给客户端

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

另一种方法是使用一个小型包装类包装现有异常,可能是一个
ObjectNotFoundException
,该类实现了使用
@Provider
注释的
ExceptionMapper
接口。这告诉JAX-RS运行时,如果引发了包装异常,则返回
ExceptionMapper
中定义的响应代码

我也希望将该QueryParam实现为字符串,然后处理转换,必要时重新引用

如果特定于区域设置的行为是所需和预期的行为,则可以使用以下命令返回400错误请求错误:

抛出新的WebApplicationException(Response.Status.BAD_请求)


有关更多选项,请参阅JavaDoc。

您还可以为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响应),但使用此类可以考虑一般的参数处理,其中可能包括日志记录等。

@Provider
@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();
}
}
公共类BadURIExceptionMapper实现ExceptionMapper{ 公共响应(NotFoundException){ 返回Response.status(未找到Response.status.NOT_)。 实体(新的ErrorResponse(exception.getClass().toString(), 异常。getMessage()))。 build(); } }

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

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();
    }
}
@Provider
公共类ParameterExceptionMapper实现ExceptionMapper{
@凌驾
公共响应(参数异常异常){
返回Response.status(status.BAD_REQUEST).entity(exception.getParameterName()+“类型不正确”).build();
}
}

@QueryParam文档说

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

1) 是基本类型
2) 具有接受单个 字符串参数
3) 具有名为valueOf或fromString的静态方法 接受单个字符串参数(例如,请参见, 整数值(字符串))
4) 注册实施 javax.ws.rs.ext.ParamConverterProvider JAX-rs扩展SPI 返回一个javax.ws.rs.ext.ParamConverter实例,该实例能够 字符串“类型的转换。
5) 是列表、集合或 SortedSet,其中T满足上述2、3或4。结果 集合是只读的。”

当字符串形式的查询参数无法转换为t类型时,若要控制用户的响应,可以抛出WebApplicationException。Dropwizard附带以下*参数类,您可以根据需要使用这些类

BooleanParam、DateTimeParam、IntParam、LongParam、LocalDateParam、NoneEmptyStringParam、UUIDParam。看

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

如果上面的列表不适合您的需要,请通过扩展AbstractParam来定义您自己的列表。重写解析方法。若您需要控制错误响应主体,请重写错误方法

Coda Hale关于这方面的好文章在

import io.dropwizard.jersey.params.AbstractParam;
导入java.util.Date;
导入javax.ws.rs.core.Response;
导入javax.ws.rs.core.Response.Status;
公共类DateParam扩展了AbstractParam{
公共日期参数(字符串输入){
超级(输入);
}
@凌驾
受保护的日期分析(字符串输入)引发异常{
返回新日期(输入);
}
@凌驾
普罗泰克
//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
}
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());
         }
}
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;
}
@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();
    }

}