首页 文章

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

提问于
浏览
205

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

@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”)?记录错误怎么样?也许在自定义 Headers 中添加问题描述(“错误日期格式”)以帮助排除故障?或者返回包含详细信息的完整错误响应以及5xx状态代码?

8 回答

  • 7

    有几种方法可以使用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());
         }
    }
    

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

    @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 .

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

    另一种方法是包装一个现有的Exception,也许是一个带有一个小包装类的ObjectNotFoundException,它实现了用@Provider注释注释的ExceptionMapper接口 . 这告诉JAX-RS运行时,如果引发了包装的Exception,则返回ExceptionMapper中定义的响应代码 .

  • 26
    @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等以提供自定义响应 .

  • 261

    当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();
        }
    }
    
  • 34

    您还可以为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响应),但使用此类允许您将通常的参数处理分解出来,其中可能包括日志记录等 .

  • 4

    一个明显的解决方案:接受一个String,自己转换为Date . 这样您就可以定义所需的格式,捕获异常并重新抛出或自定义发送的错误 . 对于解析,SimpleDateFormat应该可以正常工作 .

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

  • 66

    我也喜欢StaxMan可能会将QueryParam实现为String,然后根据需要处理转换,重新抛出 .

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

    throw new WebApplicationException(Response.Status.BAD_REQUEST);

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

  • 11

    @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 Be List,Set或SortedSet,其中T满足上面的2,3或4 . 结果集合是只读的 . ”

    如果要在String形式的查询参数无法转换为类型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日期时间 .

  • 1

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

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

相关问题