Java JAX-RS/Jersey如何定制错误处理?
我正在使用Jersey学习JAX-RS(又名JSR-311)。我已经成功地创建了根资源,正在使用参数: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
@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();
}
}