开发者

How Jersey extracts generic type from Collections to invoke javax.ws.rs.ext.MessageBodyWriter#writeTo()?

开发者 https://www.devze.com 2023-01-04 05:57 出处:网络
In a Rest Service using the JAX-RS specification, I can define a generic service like @GET @Path(\"something\")

In a Rest Service using the JAX-RS specification, I can define a generic service like

@GET
@Path("something")
@Produces( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public List<MyPojo> getMyPojoList() {
    ...
}

Something magic happens 开发者_Go百科in Jersey because when invoking

javax.ws.rs.ext.MessageBodyWriter#writeTo(T t,
        Class<?> type,
        Type genericType,
        Annotation annotations[], 
        MediaType mediaType, 
        MultivaluedMap<String, Object> httpHeaders,
        OutputStream entityStream) throws IOException, WebApplicationException;

when analyzing the genericType it is easy to see that his value is class MyPojo.

I've been trying to read the Jersey source code to understand how they extract the Collection Generic type of before invoking the method writeTo of javax.ws.rs.ext.MessageBodyWriter, but I got lost once arrived to read the GenericEntity class.

Can anyone help me to understand which kind of magic they use there? Thanks in advance!!!


Some generic information is retained through the compiler into runtime. Take a look at the documentation on Java reflection. In this case they probably use getGenericReturnType, although there are a host of methods relating to parameter types and even the class's generic parameters.

The most obvious shortcoming here is that only information from compile time may be kept - in particular you cannot reliably inspect a List to determine whether it is a List<Integer> or List<String>.


You need to use the magical classes of the JAX-RS framework to preserve the information you're looking for. Took me quite some time to actually get this working on my project as well, but this is how I solved the problem:

First, inside your Resource class, pass the generic collection as such (note I use Response Objects):

public Response findAll() {
  Collection<MyType> entities = service.findAll(); // whatever code you need to load it.
  GenericEntity<Collection<MyType>> list = new GenericEntity<Collection<MyType>>(entities) {}; // This line is key!
  return Response.ok.entity(list).build();
}

Then you can create a normal MessageBodyWriter implementation and perform the type-checking in the method that is responsible for checking whether your implementation is suitable or not.

public class MyTypeMessageBodyWriter implements MessageBodyWriter<MyType> {
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        if (Collection.class.isAssignableFrom(type) && genericType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            Type[] actualTypeArgs = parameterizedType.getActualTypeArguments();
            return (actualTypeArgs.length == 1 && actualTypeArgs[0].equals(MyType.class));
        } else {
            return false;
        }
    }
}

You can enhance the isWriteable method to perform more checks like double check whether the MediaType is the correct one for example, and if some annotations need to be present etc, depending on your requirements.

But to summerize:

  • Wrap your collection in the GenericEntity<> wrapper to preserver generic type info
  • Perform the proper casts inside the MessageBodyWriter to determine the type.
0

精彩评论

暂无评论...
验证码 换一张
取 消