Using Spring Resttemplate in Generic Method with Generic Parameter

Using Spring RestTemplate in generic method with generic parameter

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.

Spring RestTemplate and generic types ParameterizedTypeReference collections like ListT

I worked around this using the following generic method:

public <T> List<T> exchangeAsList(String uri, ParameterizedTypeReference<List<T>> responseType) {
return restTemplate.exchange(uri, HttpMethod.GET, null, responseType).getBody();
}

Then I could call:

List<MyDto> dtoList = this.exchangeAsList("http://my/url", new ParameterizedTypeReference<List<MyDto>>() {});

This did burden my callers with having to specify the ParameterizedTypeReference when calling, but meant that I did not have to keep a static mapping of types like in vels4j's answer 

Generic restTemplateExchange with generic responseType

I found this answer, and it was working for me:

Unable to get a generic ResponseEntity<T> where T is a generic class "SomeClass<SomeGenericType>"

RestTemplate restTemplate = new RestTemplate();
restTemplate.exchange(pUrl, HttpMethod.POST, pEntity, new ParameterizedTypeReference<ResultListPage<MyObject>>() {});

RestTemplate use with a generic response type

can use String

ResponseEntity<String> result = getRestTemplate().exchange(uri, HttpMethod.POST, httpEntity,
new ParameterizedTypeReference<String>() {
}, userId);

Generics with Spring RESTTemplate

ParameterizedTypeReference has been introduced in 3.2 M2 to workaround this issue.

Wrapper<Model> response = restClient.exchange(loginUrl, 
HttpMethod.GET,
null,
new ParameterizedTypeReference<Wrapper<Model>>() {}).getBody();

However, the postForObject/getForObject variant was not introduced.

Spring RestTemplate: how to define the parameter type in a generic class

So I found a solution, which is to override the getType method of ParameterizedTypeReference returning a custom made ParameterizedType like this:

public abstract class GenericClient<DTO> {
private final Class<DTO> dtoClass;
... ctor which sets dtoClass ...

public List<DTO> findAll() {
...
URI uri = ...
ResponseEntity<List<DTO>> exchange = getRestTemplate()
.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference<List<DTO>() {
@Override
public Type getType() {
return new ParameterizedType() {
@Override
public Type getRawType() {
return List.class;
}

@Override
public Type getOwnerType() {
return null;
}

@Override
public Type[] getActualTypeArguments() {
return new Type[]{dtoClass};
}
};
});
return exchange.getBody(); // OK
}
}

Why doesn't this code work when wrapped in a generic function?

Answer for 1.

ParameterizedTypeReference<X> typeRef = new ParameterizedTypeReference<X>() {};

Thanks the final {} jackson is able to find out the what X is in run-time using reflection however X is resolved in compilation time so if you have MyClass or T that is exactly what it will get in runtime; It won't be able to figure out what the T is assigned to in runtime.

For the very same reason, if you keep using the function-less option but you remove the {} at the end it will compile but it will result in the same error.

Answer for 2.

Instead of Class<T> returnType, that you never make reference to btw, you could pass ParameterizedTypeReference<T> typeRef directly. The code that call the post would then need to determine T in compilation time:

@Test
public void with_function() {
ParameterizedTypeReference<MyClass> typeRef = new ParameterizedTypeReference<>() {};
MyClass returnValue = post("some text", typeRef, "/test");
}
}

However I think you should consider alternatives that do not rely on the {} trick which might be problematic.

Have you tried ParameterizedTypeReference's forType?:

public <T> T post(Object content, Class<T> returnType, String url){
ParameterizedTypeReference<T> typeRef = ParameterizedTypeReference.forType(returnType);
HttpEntity<Object> requestEntity = new HttpEntity<>(content);
ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, typeRef);
return response.getBody();
}

In any case this will work with non-generic assignations to T like MyClass as when passing MyClass.class as the return type; It would not work with ArrayList<MyClass> list; list.getClass() since it would be equivalent to return ArrayList.class. I guess in those cases you would need to construct and pass a different Type instance that would correspond to the more complex type expression.



Related Topics



Leave a reply



Submit