自定义球衣参数抛出异常时的MultiException

MultiException when custom jersey param throws exception

注意:可在 https://gist.github.com/SrikanthRao/c9fc35e6fe22a74ab40c

获取重现此问题的所有代码

http://localhost:8080/date/bean?date=2014-13-23(使用 BeanParam)产生 "{"code":500,"message":"There was an error processing your request. It has been logged (ID 48be9aa43bd49547)."}" 没有将 MultiExceptionMapper 添加到球衣。

如果我将 MultiExceptionMapper 添加到 jersey,上面的 url 结果是

"Date is either not in YYYY-MM-DD format or is invalid"

http://localhost:8080/date?date=2014-13-23(直接@QueryParam参数)产生 "Date is either not in YYYY-MM-DD format or is invalid"

几个问题:

  1. 这是以更简洁的方式处理输入验证的正确方法吗?
  2. 我希望这无需添加我自己的 MultiExceptionMapper 即可工作。 Jersey 不支持在资源方法中作为 @BeanParam 注入的 POJO 中的自定义 *Params 吗?

这是请求时产生的堆栈跟踪(没有将 MultiExceptionMapper 添加到球衣)。当然删除长痕迹。如果您需要完整的堆栈跟踪,请告诉我。

    ERROR [2015-05-04 18:48:33,366] io.dropwizard.jersey.errors.LoggingExceptionMapper: Error handling a request: 0f23e4de758653d6
    ! javax.ws.rs.WebApplicationException: HTTP 400 Bad Request
    ! at io.dropwizard.jersey.params.AbstractParam.<init>(AbstractParam.java:28) ~[dropwizard-jersey-0.8.1.jar:0.8.1]
    ! at com.fun.myapp.LocalDateTimeParam.<init>(LocalDateTimeParam.java:20) ~[classes/:na]
    ! at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_45]
...
...
Causing: org.glassfish.hk2.api.MultiException: A MultiException has 3 exceptions.  They are:
! 1. javax.ws.rs.WebApplicationException: HTTP 400 Bad Request
! 2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.fun.myapp.PaginationFilters errors were found
! 3. java.lang.IllegalStateException: Unable to perform operation: resolve on com.fun.myapp.PaginationFilters
! 
! at org.jvnet.hk2.internal.Collector.throwIfErrors(Collector.java:88) ~[hk2-locator-2.4.0-b10.jar:na]
! at org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:252) ~[hk2-locator-2.4.0-b10.jar:na]
! at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:360) ~[hk2-locator-2.4.0-b10.jar:na]
! at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:471) ~[hk2-locator-2.4.0-b10.jar:na]
....
....
WARN  [2015-05-04 18:48:33,401] org.glassfish.jersey.internal.Errors: The following warnings have been detected: WARNING: Unknown HK2 failure detected:
MultiException stack 1 of 3
javax.ws.rs.WebApplicationException: HTTP 400 Bad Request
    at io.dropwizard.jersey.params.AbstractParam.<init>(AbstractParam.java:28)
    at com.fun.myapp.LocalDateTimeParam.<init>(LocalDateTimeParam.java:20)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
...
...
MultiException stack 2 of 3
java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.fun.myapp.PaginationFilters errors were found
    at org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:249)
    at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:360)
    at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:471)
...
...
MultiException stack 3 of 3
java.lang.IllegalStateException: Unable to perform operation: resolve on com.fun.myapp.PaginationFilters
    at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:389)
    at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:471)
    at org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:162)

我已经在 dropwizard-user google 组中问过这个问题 - https://groups.google.com/forum/#!topic/dropwizard-user/yW-RXSSlspY

问题 1

根据 dropwizard 的核心文档,我看到了两种可能的输入验证实现方式:

  • 通过验证注解

you can add validation annotations to fields of your representation classes and validate them...

这似乎不适合你的情况。事实上,LocalDateTime 没有可用的注释,创建一个注释会导致解析 LocalDateTime 两次:用于验证和设置 bean 字段。

  • 通过错误处理:

If you want more control, you can also declare JerseyProviders in your Environment to map Exceptions to certain responses by calling JerseyEnvironment#register(Object) with an implementation of javax.ws.rs.ext.ExceptionMapper...

为了回答您的第一个问题,我会说在您的情况下使用异常映射器完全没问题。

问题 2 调试两个 isValidDate 方法表明 @BeanParam 版本使用 ClazzCreator.java 而 @QueryParam 不使用。此 class 负责创建 bean class 实例。如果您检查 the class 的第 226 行,您会看到它在解析具有多个错误的输入时收集了多个异常。这应该允许报告与不同 bean 字段相关的几个错误,包括一些后续异常。

答案是 Jeysey 支持 POJO 中的 *Params。但是,相关异常封装在@BeanParam 上下文中的 MultiException 中。 因此,如果您计划向 PaginationFilters 添加其他字段,则应考虑在映射的异常消息中报告多个错误。

1/ 为了验证日期,我更喜欢自己解析 "dates"。它简单干净。这是一些工作代码:

package com.rizze.Whosebug;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;



@Path("/test")
public class TestRessource {


    @GET    
    @Path("is")
    @Produces({"application/json"})
    public Response isDateValid(@QueryParam("date") String dateString){

        if(dateString == null) 
            return Response.status(400).entity("date is null").build();
        Date thedate =null;
        try {
            thedate = new SimpleDateFormat("yyyy-mm-dd").parse(dateString);
        } 
        catch (ParseException e) {
            return Response.status(400).entity("date malformed").build();
        }
        System.out.println("time:"+thedate.getTime());
        return Response.ok()
                .entity("{\"time\":"+ thedate.getTime() +"}")
                .header("source", "com.rizze")
                .build();
    }
}

在此代码中,您将调用

http://localhost:8080/test/is?date=2014-12-12

将 return :

{"time":1389496320000}

看一下要点:https://gist.github.com/jeorfevre/6e46ae8d9232f7f9d7cc

2/ 为了捕获异常,一个好的做法是使用提供程序捕获服务器级别的异常(通过向您的应用程序注册提供程序)。 对于应用程序级别的异常,请使用 response.status(异常状态) .....

自行利用您的异常

请看一下我的 fork gist,我添加了一个 _ServerError.class 并在应用程序中注册了它:

  //register a mapper by rizze
        environment.jersey().register(_ServerError.class);

请看官方文档,非常清楚: official jersey representation doc