Using Java-generics template type in RESTful Respo

2020-05-20 08:45发布

问题:

I have a generic JAX-RS resource class and I have defined a generic findAll method

public abstract class GenericDataResource<T extends GenericModel> {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response findAll() {
        Query query = em.createNamedQuery(modelClass.getSimpleName()+".findAll");
        List<T> list = query.getResultList();
        return Response.ok(new GenericEntity<List<T>>(list) {}).build();
    }
}

and User class:

public class User extends GenericModel {
    ...
}

And here is example subclass definition:

@Path("users")
public class UserResource extends GenericDataResource<User> {

    public UserResource() {
        super(User.class);
    }
}

I get below Exception:

com.sun.jersey.api.MessageException: A message body writer for Java class 
java.util.Vector, and Java type java.util.List<T>, 
and MIME media type application/json was not found exception.

If I replace T with a defined class such as User like so:

GenericEntity<List<User>>(list)

then it works fine.

Any idea as to how I can make it work with generic T?

回答1:

Once the source code is compiled, the (anonymous) class created by the line:

new GenericEntity<List<T>>(list) {}

uses a type variable to refer to its parent. Since type variables have no value at runtime, you can't use generics like this. You are forced to pass a so-called type token from the calling site. This is an example which requires the token to be passed from the caller of findAll(), but you could require one in the constructor and save it in an instance variable as well:

public abstract class GenericDataResource<T extends GenericModel> {
  public Response findAll(GenericEntity<List<T>> token) {
    Query query = em.createNamedQuery(modelClass.getSimpleName() + ".findAll");
    List<T> list = query.getResultList();
    return Response.ok(token).build();
  }
}

The caller will send a token like

new GenericEntity<List<User>>() {}

If you only use non-parameterized subclasses, findAll() may take advantage of reflection to build the token (untested, hope you get the idea):

@GET
@Produces(MediaType.APPLICATION_JSON)
public Response findAll() {
  Query query = em.createNamedQuery(modelClass.getSimpleName()+".findAll");
  List<T> list = query.getResultList();
  return Response.ok(new GenericEntity(list, getType())).build();
}

You have to implement getType() to return the desired type. It will be a subclass of ParameterizedType capable of denoting the type List<DAO<User>>



回答2:

In addition to the answers, to read the Response object back on the client :

List<MyObject> list = response.readEntity(new GenericType<List<MyObject>>(){}));


回答3:

Response object gets returned to client in response to request sent to a server. The Response class has Response.ResponseBuilder inner class that collects each properties set to its field of type Response.ResponseBuilder. Response.ResponseBuilder applies the Builder design pattern to construct Response Object.

The build() method is responsible to connect the chain of Response.ResponseBuilder objects formed in the course of build. E.g.

Response.status(200);   

status method return Response.ResponseBuilder after assigning STATUS

 Response.status(200).entity( AnyObj );

entity object assigns the entity( the payload returned back) of type Response.ResponseBuilder and gets assigned to the Response's instance varible of. After that, status also assigns the status and returns Response.ResponseBuilder instance. The builder connects them at time of build() method call.

Response.status(200).entity( obj ).build(); 

Finally, the build method constructs complete(with properties set forth) Response.

Now comes the question of GenericEntity object. It Represents a response entity of a generic type T.

For Instance,

GenericEntity> obj = new GenericEntity>(lst) {}; Response.status(200).entity( obj ).build();

obj is type of List as given above. entity method accepts Object type which is generic. In case, the need of specific type arises, you got to pass it as argument of GenericEntity type by which at runtime it's casted to object of Specific type.

Practical Use

Wanted for my Jersey Framework to respond JSON of Array type which was List object in my Java model back to client as part of Response object.

Therefore, new GenericEntity> --- casts the object/payload to List type new GenericEntity --- runtime type becomes String


Resource on Webservice Side

public Response findAllFruits(@QueryParam("frtID") String frtID ) {

         List<String> lst = new ArrayList<String>();
         lst.add("Banana");
         lst.add("Apple");

         GenericEntity<List<String>> obj = new GenericEntity<List<String>>(lst) {};


          return Response.status(200).entity( obj ).build(); 

    } 

Output Response sent back to client. [ "Banana", "Apple" ]


How to Read Response Entity

List<CustomType> myObj= response.readEntity(new GenericType<List<CustomType>>() {});


回答4:

To solve it a made a method to return a GenericEntity and then a invoke it from my generic class, some thing like below:

@XmlRootElement
public abstract class AbstractRest<T extends IEntity> {

    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}


public abstract class SmartWebServiceNEW<R extends AbstractRest<T>> {

    @GET
    @Produces({MediaType.APPLICATION_XML})
    public Response findAll() {
        List<T> lista = getDelegate().findAll();
        if (lista == null || lista.isEmpty()) {
            return Response.status(Response.Status.NO_CONTENT).build();
        }
        List<R> retorno = new ArrayList<R>();
        for (T it : lista) {
            retorno.add(toRest(it));
        }
        GenericEntity entity = listToGenericEntity(retorno);
        return Response.ok(entity).build();
    }

    protected abstract GenericEntity listToGenericEntity(List<R> restList);   
}

@Path("/something")
@RequestScoped
public class MyEntityResource extends SmartWebServiceNEW<MyEntityExtendingAbstractRest> {

 @Override
    protected GenericEntity listToGenericEntity(List<MyEntityExtendingAbstractRest> restList) {
        return new GenericEntity<List<MyEntityExtendingAbstractRest>>(restList) {
        };
    }

}