parameterizedtypereference example java spring generics jackson resttemplate

java - example - Usar Spring RestTemplate en método genérico con parámetro genérico



parameterizedtypereference example (7)

Para usar tipos genéricos con Spring RestTemplate necesitamos usar ParameterizedTypeReference ( No se puede obtener un ResponseEntity genérico <T> donde T es una clase genérica "SomeClass <SomeGenericType>" )

Supongamos que tengo alguna clase

public class MyClass { int users[]; public int[] getUsers() { return users; } public void setUsers(int[] users) {this.users = users;} }

Y alguna clase de envoltura

public class ResponseWrapper <T> { T response; public T getResponse () { return response; } public void setResponse(T response) {this.response = response;} }

Entonces, si intento hacer algo como esto, todo está bien.

public ResponseWrapper<MyClass> makeRequest(URI uri) { ResponseEntity<ResponseWrapper<MyClass>> response = template.exchange( uri, HttpMethod.POST, null, new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}); return response; }

Pero cuando intento crear una variante genérica del método anterior ...

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; }

... y llamando a este método así ...

makeRequest(uri, MyClass.class)

... en lugar de obtener el objeto ResponseEntity<ResponseWrapper<MyClass>> objeto ResponseEntity<ResponseWrapper<LinkedHashSet>> .

¿Como puedó resolver esté problema? ¿Es un error RestTemplate?

ACTUALIZACIÓN 1 Gracias a @Sotirios entiendo el concepto. Desafortunadamente estoy recién registrado aquí, así que no puedo comentar su respuesta, así que escribirla aquí. No estoy seguro de entender claramente cómo implementar el enfoque propuesto para resolver mi problema con Map with Class key (Propuesto por @Sotirios al final de su respuesta). ¿Alguien le importaría dar un ejemplo?


Como explica Sotirios, no puede usar ParameterizedTypeReference , pero ParameterizedTypeReference se usa solo para proporcionar Type al asignador de objetos, y como tiene la clase que se elimina cuando ocurre el borrado de tipos, puede crear su propio ParameterizedType y pasarlo a RestTemplate , para que el mapeador de objetos pueda reconstruir el objeto que necesita.

Primero necesita tener la interfaz ParameterizedType implementada, puede encontrar una implementación en el proyecto Google Gson here . Una vez que agregue la implementación a su proyecto, puede extender el resumen ParameterizedTypeReference siguiente manera:

class FakeParameterizedTypeReference<T> extends ParameterizedTypeReference<T> { @Override public Type getType() { Type [] responseWrapperActualTypes = {MyClass.class}; ParameterizedType responseWrapperType = new ParameterizedTypeImpl( ResponseWrapper.class, responseWrapperActualTypes, null ); return responseWrapperType; } }

Y luego puedes pasar eso a tu función de intercambio:

template.exchange( uri, HttpMethod.POST, null, new FakeParameterizedTypeReference<ResponseWrapper<T>>());

Con todo el tipo de información presente objeto mapeador construirá correctamente su objeto ResponseWrapper<MyClass>


Como lo muestra el código a continuación, funciona.

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(); } }


En realidad, puedes hacer esto, pero con código adicional.

Hay un equivalente de Guava de ParameterizedTypeReference y se llama TypeToken .

La clase de Guava es mucho más poderosa que el equivalente de Spring. Puede componer TypeTokens como lo desee. Por ejemplo:

static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken) { return new TypeToken<Map<K, V>>() {} .where(new TypeParameter<K>() {}, keyToken) .where(new TypeParameter<V>() {}, valueToken); }

Si llama a mapToken(TypeToken.of(String.class), TypeToken.of(BigInteger.class)); creará TypeToken<Map<String, BigInteger>> !

La única desventaja aquí es que muchas API de Spring requieren ParameterizedTypeReference y no TypeToken . Pero podemos crear la implementación ParameterizedTypeReference , que es un adaptador para TypeToken .

import com.google.common.reflect.TypeToken; import org.springframework.core.ParameterizedTypeReference; import java.lang.reflect.Type; public class ParameterizedTypeReferenceBuilder { public static <T> ParameterizedTypeReference<T> fromTypeToken(TypeToken<T> typeToken) { return new TypeTokenParameterizedTypeReference<>(typeToken); } private static class TypeTokenParameterizedTypeReference<T> extends ParameterizedTypeReference<T> { private final Type type; private TypeTokenParameterizedTypeReference(TypeToken<T> typeToken) { this.type = typeToken.getType(); } @Override public Type getType() { return type; } @Override public boolean equals(Object obj) { return (this == obj || (obj instanceof ParameterizedTypeReference && this.type.equals(((ParameterizedTypeReference<?>) obj).getType()))); } @Override public int hashCode() { return this.type.hashCode(); } @Override public String toString() { return "ParameterizedTypeReference<" + this.type + ">"; } } }

Entonces puedes usarlo así:

public <T> ResponseWrapper<T> makeRequest(URI uri, Class<T> clazz) { ParameterizedTypeReference<ResponseWrapper<T>> responseTypeRef = ParameterizedTypeReferenceBuilder.fromTypeToken( new TypeToken<ResponseWrapper<T>>() {} .where(new TypeParameter<T>() {}, clazz)); ResponseEntity<ResponseWrapper<T>> response = template.exchange( uri, HttpMethod.POST, null, responseTypeRef); return response; }

Y llámalo así:

ResponseWrapper<MyClass> result = makeRequest(uri, MyClass.class);

¡Y el cuerpo de respuesta se deserializará correctamente como ResponseWrapper<MyClass> !

Incluso puede usar tipos más complejos si reescribe su método de solicitud genérico (o sobrecargarlo) de esta manera:

public <T> ResponseWrapper<T> makeRequest(URI uri, TypeToken<T> resultTypeToken) { ParameterizedTypeReference<ResponseWrapper<T>> responseTypeRef = ParameterizedTypeReferenceBuilder.fromTypeToken( new TypeToken<ResponseWrapper<T>>() {} .where(new TypeParameter<T>() {}, resultTypeToken)); ResponseEntity<ResponseWrapper<T>> response = template.exchange( uri, HttpMethod.POST, null, responseTypeRef); return response; }

De esta forma, T puede ser de tipo complejo, como List<MyClass> .

Y llámalo así:

ResponseWrapper<List<MyClass>> result = makeRequest(uri, new TypeToken<List<MyClass>>() {});


No, no es un error. Es el resultado de cómo funciona el hack ParameterizedTypeReference .

Si observa su implementación, utiliza la Class#getGenericSuperclass() que indica

Devuelve el tipo que representa la superclase directa de la entidad (clase, interfaz, tipo primitivo o vacío) representada por esta clase.

Si la superclase es un tipo parametrizado, el objeto Type devuelto debe reflejar con precisión los parámetros de tipo reales utilizados en el código fuente.

Entonces, si usas

new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}

devolverá con precisión un Type para ResponseWrapper<MyClass> .

Si utiliza

new ParameterizedTypeReference<ResponseWrapper<T>>() {}

devolverá con precisión un Type para ResponseWrapper<T> porque así es como aparece en el código fuente.

Cuando Spring ve T , que en realidad es un objeto TypeVariable , no sabe qué tipo usar, por lo que usa su valor predeterminado.

No puede usar ParameterizedTypeReference la manera que está proponiendo, haciéndolo genérico en el sentido de aceptar cualquier tipo. Considere la posibilidad de escribir un Map con la Class clave asignada a una referencia ParameterizedTypeReference para esa clase.

Puede subclase ParameterizedTypeReference y anular su método getType para devolver un ParameterizedType creado adecuadamente, como lo sugiere IonSpin .


Podrías hacer lo siguiente sin usar la clase contenedora. Intenta usar el poder real si genéricos

/** * * Method for GET Operations * * @param url url to send request * @return returned json String * @throws Exception exception thrown */ public List<T> getJSONString(String url, Class<T[]> clazz) throws Exception { logger.debug("getJSONString() : Start"); List<T> response = null; ResponseEntity<T[]> responseEntity = null; List<String> hostList = Arrays.asList(propertyFileReader.getRestApiHostList().split("//s*,//s*")); Iterator<String> hostListIter = hostList.iterator(); String host = null; while (true) { try { host = hostListIter.next(); logger.debug("getJSONString() : url={}", (host + url)); responseEntity = restTemplate.getForEntity(host + url, clazz); if (responseEntity != null) { response = Arrays.asList(responseEntity.getBody()); break; } } catch (RestClientException ex) { if (!hostListIter.hasNext()) { throw ex; } logger.debug("getJSONString() : I/O exception {} occurs when processing url={} ", ex.getMessage(), (host + url)); } } return response; }


Tengo otra forma de hacerlo ... Supongamos que cambias tu convertidor de mensajes a String para tu RestTemplate, y luego puedes recibir JSON sin procesar. Con el JSON sin formato, puede asignarlo a su colección genérica utilizando un mapeador de objetos Jackson. Así es cómo:

Cambie el convertidor de mensajes:

List<HttpMessageConverter<?>> oldConverters = new ArrayList<HttpMessageConverter<?>>(); oldConverters.addAll(template.getMessageConverters()); List<HttpMessageConverter<?>> stringConverter = new ArrayList<HttpMessageConverter<?>>(); stringConverter.add(new StringHttpMessageConverter()); template.setMessageConverters(stringConverter);

Luego obtenga su respuesta JSON así:

ResponseEntity<String> response = template.exchange(uri, HttpMethod.GET, null, String.class);

Procese la respuesta de esta manera:

String body = null; List<T> result = new ArrayList<T>(); ObjectMapper mapper = new ObjectMapper(); if (response.hasBody()) { body = items.getBody(); try { result = mapper.readValue(body, mapper.getTypeFactory().constructCollectionType(List.class, clazz)); } catch (Exception e) { e.printStackTrace(); } finally { template.setMessageConverters(oldConverters); } ...


Nota: Esta respuesta se refiere / agrega a la respuesta y comentario de Sotirios Delimanolis.

Intenté que funcione con Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>> , como se indica en el comentario de Sotirios, pero no podría hacerlo sin un ejemplo.

Al final, dejé caer el comodín y la parametrización de ParameterizedTypeReference y usé tipos crudos, como así

Map<Class<?>, ParameterizedTypeReference> typeReferences = new HashMap<>(); typeReferences.put(MyClass1.class, new ParameterizedTypeReference<ResponseWrapper<MyClass1>>() { }); typeReferences.put(MyClass2.class, new ParameterizedTypeReference<ResponseWrapper<MyClass2>>() { }); ... ParameterizedTypeReference typeRef = typeReferences.get(clazz); ResponseEntity<ResponseWrapper<T>> response = restTemplate.exchange( uri, HttpMethod.GET, null, typeRef);

y esto finalmente funcionó.

Si alguien tiene un ejemplo con parametrización, estaría muy agradecido de verlo.