可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I\'m learning JAX-RS (aka, JSR-311) using Jersey. I\'ve successfuly created a Root Resource and am playing around with parameters:
@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
}
}
This works great, and handles any format in the current locale which is understood by the Date(String) constructor (like YYYY/mm/dd and mm/dd/YYYY). But if I supply a value which is invalid or not understood, I get a 404 response.
For example:
GET /hello?name=Mark&birthDate=X
404 Not Found
How can I customize this behavior? Maybe a different response code (probably \"400 Bad Request\")? What about logging an error? Maybe add a description of the problem (\"bad date format\") in a custom header to aid troubleshooting? Or return a whole Error response with details, along with a 5xx status code?
回答1:
There are several approaches to customize the error handling behavior with JAX-RS. Here are three of the easier ways.
The first approach is to create an Exception class that extends WebApplicationException.
Example:
public class NotAuthorizedException extends WebApplicationException {
public NotAuthorizedException(String message) {
super(Response.status(Response.Status.UNAUTHORIZED)
.entity(message).type(MediaType.TEXT_PLAIN).build());
}
}
And to throw this newly create Exception you simply:
@Path(\"accounts/{accountId}/\")
public Item getItem(@PathParam(\"accountId\") String accountId) {
// An unauthorized user tries to enter
throw new NotAuthorizedException(\"You Don\'t Have Permission\");
}
Notice, you don\'t need to declare the exception in a throws clause because WebApplicationException is a runtime Exception. This will return a 401 response to the client.
The second and easier approach is to simply construct an instance of the WebApplicationException directly in your code. This approach works as long as you don\'t have to implement your own application Exceptions.
Example:
@Path(\"accounts/{accountId}/\")
public Item getItem(@PathParam(\"accountId\") String accountId) {
// An unauthorized user tries to enter
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
This code too returns a 401 to the client.
Of course, this is just a simple example. You can make the Exception much more complex if necessary, and you can generate what ever http response code you need to.
One other approach is to wrap an existing Exception, perhaps an ObjectNotFoundException with an small wrapper class that implements the ExceptionMapper interface annotated with a @Provider annotation. This tells the JAX-RS runtime, that if the wrapped Exception is raised, return the response code defined in the ExceptionMapper.
回答2:
@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();
}
}
Create above class. This will handle 404 (NotFoundException) and here in toResponse method you can give your custom response. Similarly there are ParamException etc. which you would need to map to provide customized responses.
回答3:
Jersey throws an com.sun.jersey.api.ParamException when it fails to unmarshall the parameters so one solution is to create an ExceptionMapper that handles these types of exceptions:
@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();
}
}
回答4:
You could also write a reusable class for QueryParam-annotated 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());
}
}
then use it like this:
private @QueryParam(\"from\") DateParam startDateParam;
private @QueryParam(\"to\") DateParam endDateParam;
// ...
startDateParam.getDate();
Although the error handling is trivial in this case (throwing a 400 response), using this class allows you to factor-out parameter handling in general which might include logging etc.
回答5:
One obvious solution: take in a String, convert to Date yourself. That way you can define format you want, catch exceptions and either re-throw or customize error being sent.
For parsing, SimpleDateFormat should work fine.
I am sure there are ways to hook handlers for data types too, but perhaps little bit of simple code is all you need in this case.
回答6:
I too like StaxMan would probably implement that QueryParam as a String, then handle the conversion, rethrowing as necessary.
If the locale specific behavior is the desired and expected behavior, you would use the following to return the 400 BAD REQUEST error:
throw new WebApplicationException(Response.Status.BAD_REQUEST);
See the JavaDoc for javax.ws.rs.core.Response.Status for more options.
回答7:
@QueryParam documentation says
\" The type T of the annotated parameter, field or property must
either:
1) Be a primitive type
2) Have a constructor that accepts a single
String argument
3) Have a static method named valueOf or fromString
that accepts a single String argument (see, for example,
Integer.valueOf(String))
4) Have a registered implementation of
javax.ws.rs.ext.ParamConverterProvider JAX-RS extension SPI that
returns a javax.ws.rs.ext.ParamConverter instance capable of a \"from
string\" conversion for the type.
5) Be List, Set or
SortedSet, where T satisfies 2, 3 or 4 above. The resulting
collection is read-only. \"
If you want to control what response goes to user when query parameter in String form can\'t be converted to your type T, you can throw WebApplicationException. Dropwizard comes with following *Param classes you can use for your needs.
BooleanParam, DateTimeParam, IntParam, LongParam, LocalDateParam, NonEmptyStringParam, UUIDParam. See https://github.com/dropwizard/dropwizard/tree/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/params
If you need Joda DateTime, just use Dropwizard DateTimeParam.
If the above list does not suit your needs, define your own by extending AbstractParam. Override parse method. If you need control over error response body, override error method.
Good article from Coda Hale on this is at 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) constructor is deprecated. I would use Java 8 date classes if you are on Java 8. Otherwise joda date time is recommended.
回答8:
This is the correct behavior actually. Jersey will try to find a handler for your input and will try to construct an object from the provided input. In this case it will try to create a new Date object with the value X provided to the constructor. Since this is an invalid date, by convention Jersey will return 404.
What you can do is rewrite and put birth date as a String, then try to parse and if you don\'t get what you want, you\'re free to throw any exception you want by any of the exception mapping mechanisms (there are several).