Using Spring RestTemplate in generic method with generic parameter
To use generic types with Spring RestTemplate we need to use ParameterizedTypeReference
(Unable to get a generic ResponseEntity<T> where T is a generic class "SomeClass<SomeGenericType>")
Suppose I have some class
public class MyClass {
int users[];
public int[] getUsers() { return users; }
public void setUsers(int[] users) {this.users = users;}
}
And some wrapper class
public class ResponseWrapper <T> {
T response;
public T getResponse () { return response; }
public void setResponse(T response) {this.response = response;}
}
So if I'm trying to do something like this, all is OK.
public ResponseWrapper<MyClass> makeRequest(URI uri) {
ResponseEntity<ResponseWrapper<MyClass>> response = template.exchange(
uri,
HttpMethod.POST,
null,
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {});
return response;
}
But when I'm trying to create generic variant of the above method ...
public <T> ResponseWrapper<T> makeRequest(URI uri, Class<T> clazz) {
ResponseEntity<ResponseWrapper<T>> response = template.exchange(
uri,
HttpMethod.POST,
null,
new ParameterizedTypeReference<ResponseWrapper<T>>() {});
return response;
}
... and calling this method like so ...
makeRequest(uri, MyClass.class)
... instead of getting ResponseEntity<ResponseWrapper<MyClass>>
object I'm getting ResponseEntity<ResponseWrapper<LinkedHashSet>>
object.
How can I solve this problem? Is it a RestTemplate bug?
UPDATE 1
Thanks to @Sotirios I understand the concept. Unfortunately I'm newly registered here so I cant comment on his answer, so writing it here. Im not sure that I clearly understand how to implement the proposed approach to solve my problem with Map
with Class
key (Proposed by @Sotirios in the end of his answer). Would someone mind to give an example?
No, it is not a bug. It is a result of how the ParameterizedTypeReference
hack works.
If you look at its implementation, it uses Class#getGenericSuperclass()
which states
Returns the Type representing the direct superclass of the entity (class, interface, primitive type or void) represented by this Class.
If the superclass is a parameterized type, the
Type
object returned must accurately reflect the actual type parameters used in the source code.
So, if you use
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}
it will accurately return a Type
for ResponseWrapper<MyClass>
.
If you use
new ParameterizedTypeReference<ResponseWrapper<T>>() {}
it will accurately return a Type
for ResponseWrapper<T>
because that is how it appears in the source code.
When Spring sees T
, which is actually a TypeVariable
object, it doesn't know the type to use, so it uses its default.
You cannot use ParameterizedTypeReference
the way you are proposing, making it generic in the sense of accepting any type. Consider writing a Map
with key Class
mapped to a predefined ParameterizedTypeReference
for that class.
You can subclass ParameterizedTypeReference
and override its getType
method to return an appropriately created ParameterizedType
, as suggested by IonSpin.
I am using org.springframework.core.ResolvableType for a ListResultEntity :
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(ListResultEntity.class, itemClass);
ParameterizedTypeReference<ListResultEntity<T>> typeRef = ParameterizedTypeReference.forType(resolvableType.getType());
So in your case:
public <T> ResponseWrapper<T> makeRequest(URI uri, Class<T> clazz) {
ResponseEntity<ResponseWrapper<T>> response = template.exchange(
uri,
HttpMethod.POST,
null,
ParameterizedTypeReference.forType(ResolvableType.forClassWithGenerics(ResponseWrapper.class, clazz)));
return response;
}
This only makes use of spring and of course requires some knowledge about the returned types (but should even work for things like Wrapper>> as long as you provide the classes as varargs )
As the code below shows it, it works.
public <T> ResponseWrapper<T> makeRequest(URI uri, final Class<T> clazz) {
ResponseEntity<ResponseWrapper<T>> response = template.exchange(
uri,
HttpMethod.POST,
null,
new ParameterizedTypeReference<ResponseWrapper<T>>() {
public Type getType() {
return new MyParameterizedTypeImpl((ParameterizedType) super.getType(), new Type[] {clazz});
}
});
return response;
}
public class MyParameterizedTypeImpl implements ParameterizedType {
private ParameterizedType delegate;
private Type[] actualTypeArguments;
MyParameterizedTypeImpl(ParameterizedType delegate, Type[] actualTypeArguments) {
this.delegate = delegate;
this.actualTypeArguments = actualTypeArguments;
}
@Override
public Type[] getActualTypeArguments() {
return actualTypeArguments;
}
@Override
public Type getRawType() {
return delegate.getRawType();
}
@Override
public Type getOwnerType() {
return delegate.getOwnerType();
}
}