我正在使用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”)?记录错误怎么样?也许在自定义标题中添加问题描述(“错误日期格式”)以帮助排除故障?或者返回包含详细信息的完整错误响应以及5xx状态代码?
答案 0 :(得分:265)
有几种方法可以使用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中定义的响应代码。
答案 1 :(得分:66)
@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等以提供自定义响应。
答案 2 :(得分:34)
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();
}
}
答案 3 :(得分:26)
您还可以为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 :(得分:11)
一个明显的解决方案:接受一个字符串,自己转换为日期。这样您就可以定义所需的格式,捕获异常并重新抛出或自定义发送的错误。 对于解析,SimpleDateFormat应该可以正常工作。
我确信有一些方法可以为数据类型挂钩处理程序,但在这种情况下,您可能只需要一些简单的代码。
答案 5 :(得分:7)
我也喜欢StaxMan可能会将QueryParam实现为String,然后根据需要处理转换,重新抛出。
如果特定于语言环境的行为是期望和预期的行为,您将使用以下命令返回400 BAD REQUEST错误:
throw new WebApplicationException(Response.Status.BAD_REQUEST);
有关更多选项,请参阅JavaDoc javax.ws.rs.core.Response.Status。
答案 6 :(得分:1)
这实际上是正确的行为。 Jersey将尝试为您的输入找到一个处理程序,并尝试从提供的输入构造一个对象。在这种情况下,它将尝试创建一个新的Date对象,其值X提供给构造函数。由于这是一个无效的日期,按照惯例,泽西岛将返回404.
你可以做的是重写并将出生日期作为字符串,然后尝试解析,如果你没有得到你想要的东西,你可以随意抛出任何异常映射机制所需的任何异常(有几个)。
答案 7 :(得分:1)
方法1:通过扩展WebApplicationException类
通过扩展WebApplicationException创建新的异常
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());
}
}
现在在需要时抛出“ RestException”。
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;
}
您可以在此link上看到完整的申请。
方法2:实施ExceptionMapper
以下映射器处理类型为'DataNotFoundException'的异常
@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();
}
}
您可以在此link上看到完整的申请。
答案 8 :(得分:0)
作为@Steven Lavine答案的扩展,如果您想打开浏览器登录窗口。我发现很难从过滤器正确返回响应(MDN HTTP Authentication),以防用户尚未通过身份验证
这有助于我构建响应以强制浏览器登录,请注意标头的其他修改。这会将状态代码设置为401,并设置使浏览器打开用户名/密码对话框的标题。
// The extended Exception class
public class NotLoggedInException extends WebApplicationException {
public NotLoggedInException(String message) {
super(Response.status(Response.Status.UNAUTHORIZED)
.entity(message)
.type(MediaType.TEXT_PLAIN)
.header("WWW-Authenticate", "Basic realm=SecuredApp").build());
}
}
// Usage in the Filter
if(headers.get("Authorization") == null) { throw new NotLoggedInException("Not logged in"); }
答案 9 :(得分:0)
我面临着同样的问题。
我想在中心位置捕获所有错误并进行转换。
以下是我如何处理它的代码。
创建以下实现ExceptionMapper
的类,并在该类上添加@Provider
批注。这将处理所有异常。
重写toResponse
方法并返回填充有自定义数据的Response对象。
//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
}