JAX-RS /泽西岛如何自定义error handling?

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

@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错误的请求”)? 怎么logging一个错误? 也许在自定义标题中添加问题描述(“错误的date格式”)以帮助解决问题? 或者返回一个包含细节的错误响应,以及一个5xx状态码?

有几种方法可以用JAX-RS自定义error handling行为。 这里有三个更简单的方法。

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

例:

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

而要抛出这个新创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子句中声明exception,因为WebApplicationException是一个运行时exception。 这将返回一个401响应给客户端。

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

例:

 @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中定义的响应代码。

 @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(); } } 

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

Jersey在解组参数失败时抛出com.sun.jersey.api.ParamException,所以一个解决scheme是创build一个ExceptionMapper来处理这些types的exception:

 @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(); } } 

你也可以为QueryParam注释的variables编写一个可重用的类

 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(); 

尽pipe在这种情况下error handling是微不足道的(抛出一个400响应),但是使用这个类可以将一般的参数处理分解出来,其中可能包括日志logging等。

一个明显的解决scheme:采取一个string,自己转换为date。 这样,你可以定义你想要的格式,捕捉exception,并重新抛出或自定义错误被发送。 为了parsing,SimpleDateFormat应该可以正常工作。

我相信也有办法挂钩数据types的处理程序,但是在这种情况下可能只需要一点简单的代码。

我也喜欢StaxMan可能实现QueryParam作为一个string,然后处理转换,必要时重新投掷 。

如果特定于语言环境的行为是期望和预期的行为,则可以使用以下内容来返回400 BAD REQUEST错误:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

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

@QueryParam文档说

“注释参数,字段或属性的typesT必须是:

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

如果您想要控制在Stringforms的查询参数不能转换为您的typesT时应该发送给用户的响应,则可以抛出WebApplicationException。 Dropwizard附带以下*参数类,您可以使用您的需求。

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来定义您自己的需求。 覆盖parsing方法。 如果您需要控制错误响应正文,请覆盖错误方法。

来自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 8date类。否则build议使用jodadate时间。

这实际上是正确的行为。 泽西将尝试为您的inputfind一个处理程序,并尝试从提供的input中构build一个对象。 在这种情况下,它将尝试使用提供给构造函数的值X创build一个新的Date对象。 由于这是一个无效date,按照惯例泽西岛将返回404。

你可以做的是重写并把出生date作为一个string,然后尝试parsing,如果你没有得到你想要的,你可以自由地抛出任何exception映射机制所需的exception(有几个)。

 abtrack class Responce { private String message ; private int code ; public String getMessage(){ return this.message ; } public void setMessage(String message){ this.message =message ; } public String getCode(){ return this.code ; } public void setCode(String code){ this.code =code ; } } @XmlRootElement(name='MyResponce') class MyResponce extends Responce { } @Path("/hello") public class HelloWorldResource { @GET @Produces("text/html") public MyResponce get( MyResponce myResponce = new MyResponce (); @QueryParam("name") String name, @QueryParam("birthDate") Date birthDate) throw WSException { try { }catch(Exception) myResponce.setCode(400); myResponce.setMessage("Exception") } return myResponce ; }