在具有泛型参数的泛型方法中使用 Spring RestTemplate
Posted
技术标签:
【中文标题】在具有泛型参数的泛型方法中使用 Spring RestTemplate【英文标题】:Using Spring RestTemplate in generic method with generic parameter 【发布时间】:2014-03-26 02:06:52 【问题描述】:要在 Spring RestTemplate 中使用泛型类型,我们需要使用 ParameterizedTypeReference
(Unable to get a generic ResponseEntity<T> where T is a generic class "SomeClass<SomeGenericType>")
假设我有一些课
public class MyClass
int users[];
public int[] getUsers() return users;
public void setUsers(int[] users) this.users = users;
还有一些包装类
public class ResponseWrapper <T>
T response;
public T getResponse () return response;
public void setResponse(T response) this.response = response;
所以,如果我尝试做这样的事情,一切都很好。
public ResponseWrapper<MyClass> makeRequest(URI uri)
ResponseEntity<ResponseWrapper<MyClass>> response = template.exchange(
uri,
HttpMethod.POST,
null,
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() );
return response;
但是当我尝试创建上述方法的通用变体时...
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;
...并像这样调用此方法...
makeRequest(uri, MyClass.class)
... 而不是得到ResponseEntity<ResponseWrapper<MyClass>>
对象我得到ResponseEntity<ResponseWrapper<LinkedHashSet>>
对象。
我该如何解决这个问题?是 RestTemplate 错误吗?
更新 1
感谢@Sotirios,我理解了这个概念。不幸的是,我刚在这里注册,所以我无法评论他的答案,所以在这里写。我不确定我是否清楚地了解如何使用Map
和Class
键(由@Sotirios 在他的回答末尾提出)解决我的问题。有人介意举个例子吗?
【问题讨论】:
您应该始终能够评论您的问题的答案。建议的解决方案是这样工作的。您创建一个Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>>
。您将预构建的 ParameterizedTypeReference
实例添加到您期望的实际类型的 Map
对应的 Class
。
@SotiriosDelimanolis - 你能为刚接触 Java 的人改写一下吗?我来自 C# 背景,所以这对我来说是非常有趣的经历。
【参考方案1】:
Abc is come object.
HttpEntity<Abc> httpEntity= new HttpEntity<>( headers );
ResponseEntity<Abc> resp = null;
resp = restCall( doUrl, HttpMethod.GET, httpEntity, new ParameterizedTypeReference<Abc>() );
//----------------------------------------------
public <T> ResponseEntity restCall( String doUrl, HttpMethod httpMethod, HttpEntity<?> httpEntity, ParameterizedTypeReference respRef )
try
return restTemplate.exchange( doUrl, httpMethod, httpEntity, respRef );
catch( HttpClientErroException exc )
do whatever
//-------------------------- can also use a generic inside
public class ComResp<T>
private T data;
public ComResp( T data )
this.data = data
ResponseEntity<ComResp<Abc>> resp = null;
resp = restCall( doUrl, HttpMethod.GET, httpEntity, new ParameterizedTypeReference<ComResp<Abc>>() );
// spring boot 2.5.3
【讨论】:
【参考方案2】:我发现这是一个更优雅的解决方案:
private static <T> ParameterizedTypeReference<BaseResponse<T>> typeReferenceOf ( Class<T> tClass )
return ParameterizedTypeReference.forType( sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make( BaseResponse.class, new Type[] tClass , null ) );
例如,给定以下 BaseResponse
和 ResponseData
类:
@Getter
@Setter
public static class BaseResponse<T>
private ResponseData<T> response;
public BaseResponse ()
public boolean hasData ()
return response != null;
public T data ()
return response.data;
@Getter
@Setter
public static final class ResponseData<T>
private T data;
public ResponseData ()
并给出一个示例get
方法,使用WebClient
:
public <T> Mono <T> get ( URI uri, Class<T> tClass )
return webClient
.get ()
.uri ( uriBuilder -> uriBuilder.pathSegment( uri.getPath() ).build() )
.exchangeToMono ( clientResponse -> clientResponse.bodyToMono( typeReferenceOf( tClass ) ) )
.flatMap ( baseResponse -> baseResponse.hasData() ? Mono.just( baseResponse.data() ) : Mono.empty() );
【讨论】:
【参考方案3】:我觉得有一种更简单的方法可以做到这一点......只需使用您想要的类型参数定义一个类。例如:
final class MyClassWrappedByResponse extends ResponseWrapper<MyClass>
private static final long serialVersionUID = 1L;
现在将上面的代码更改为此,它应该可以工作:
public ResponseWrapper<MyClass> makeRequest(URI uri)
ResponseEntity<MyClassWrappedByResponse> response = template.exchange(
uri,
HttpMethod.POST,
null,
MyClassWrappedByResponse.class
return response;
【讨论】:
【参考方案4】:我自己的通用 restTemplate 调用实现:
private <REQ, RES> RES queryRemoteService(String url, HttpMethod method, REQ req, Class reqClass)
RES result = null;
try
long startMillis = System.currentTimeMillis();
// Set the Content-Type header
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(new MediaType("application","json"));
// Set the request entity
HttpEntity<REQ> requestEntity = new HttpEntity<>(req, requestHeaders);
// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();
// Add the Jackson and String message converters
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
// Make the HTTP POST request, marshaling the request to JSON, and the response to a String
ResponseEntity<RES> responseEntity = restTemplate.exchange(url, method, requestEntity, reqClass);
result = responseEntity.getBody();
long stopMillis = System.currentTimeMillis() - startMillis;
Log.d(TAG, method + ":" + url + " took " + stopMillis + " ms");
catch (Exception e)
Log.e(TAG, e.getMessage());
return result;
为了添加一些上下文,我正在使用 RESTful 服务,因此所有请求和响应都被包装到像这样的小型 POJO 中:
public class ValidateRequest
User user;
User checkedUser;
Vehicle vehicle;
和
public class UserResponse
User user;
RequestResult requestResult;
调用它的方法如下:
public User checkUser(User user, String checkedUserName)
String url = urlBuilder()
.add(USER)
.add(USER_CHECK)
.build();
ValidateRequest request = new ValidateRequest();
request.setUser(user);
request.setCheckedUser(new User(checkedUserName));
UserResponse response = queryRemoteService(url, HttpMethod.POST, request, UserResponse.class);
return response.getUser();
是的,还有一个 List dto-s。
【讨论】:
【参考方案5】:我正在使用 org.springframework.core.ResolvableType 作为 ListResultEntity :
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(ListResultEntity.class, itemClass);
ParameterizedTypeReference<ListResultEntity<T>> typeRef = ParameterizedTypeReference.forType(resolvableType.getType());
所以在你的情况下:
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;
这仅使用 spring,当然需要一些关于返回类型的知识(但甚至应该适用于 Wrapper>> 之类的东西,只要你将类作为 varargs 提供)
【讨论】:
我想你的意思是 ResolvableType.forClassWithGenerics(ResponseWrapper.class, clazz).getType() ,对吧?您的帖子正在我的项目中工作,我认为它是最短和最简单的。谢谢贾斯汀! @Blangero 我收到 java.lang.IllegalArgumentException:为您的评论指定的泛型数量不匹配。【参考方案6】:实际上,您可以这样做,但需要额外的代码。
Guava 等同于 ParameterizedTypeReference,它被称为 TypeToken。
Guava 的类比 Spring 的类更强大。 您可以根据需要编写 TypeToken。 例如:
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);
如果您调用mapToken(TypeToken.of(String.class), TypeToken.of(BigInteger.class));
,您将创建TypeToken<Map<String, BigInteger>>
!
这里唯一的缺点是许多 Spring API 需要 ParameterizedTypeReference
而不是 TypeToken
。但是我们可以创建ParameterizedTypeReference
实现,它是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 + ">";
那么你可以这样使用它:
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;
然后这样称呼它:
ResponseWrapper<MyClass> result = makeRequest(uri, MyClass.class);
并且响应正文将被正确反序列化为ResponseWrapper<MyClass>
!
如果您像这样重写您的通用请求方法(或重载它),您甚至可以使用更复杂的类型:
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;
这样T
可以是复杂类型,比如List<MyClass>
。
然后这样称呼它:
ResponseWrapper<List<MyClass>> result = makeRequest(uri, new TypeToken<List<MyClass>>() );
【讨论】:
这适用于我的用例,这是一种使用 GitHub 样式的链接头解析分页数据的通用机制。它为我提供了一种创建有效类型以传递给 Spring HttpMessageConverterExtractor 的方法,让我可以将接口与我的类保持通用。 多么棒的解决方案!像魅力一样工作。 从 Spring 4.3.12 开始,您可以使用ParameterizedTypeReference.forType(typeToken.getType())
。【参考方案7】:
不,这不是错误。这是ParameterizedTypeReference
hack 工作方式的结果。
如果你查看它的实现,它使用Class#getGenericSuperclass()
声明
返回表示实体的直接超类的类型 (类、接口、原始类型或 void)由该类表示。
如果超类是参数化类型,返回
Type
对象 必须准确反映源中使用的实际类型参数 代码。
所以,如果你使用
new ParameterizedTypeReference<ResponseWrapper<MyClass>>()
它将准确地为ResponseWrapper<MyClass>
返回一个Type
。
如果你使用
new ParameterizedTypeReference<ResponseWrapper<T>>()
它将准确地为ResponseWrapper<T>
返回一个Type
,因为它在源代码中就是这样显示的。
当 Spring 看到 T
,实际上是一个 TypeVariable
对象时,它不知道要使用的类型,所以它使用它的默认值。
你不能像你提议的那样使用ParameterizedTypeReference
,让它在接受任何类型的意义上都是通用的。考虑编写一个 Map
,其中键 Class
映射到该类的预定义 ParameterizedTypeReference
。
您可以继承ParameterizedTypeReference
并覆盖其getType
方法以返回适当创建的ParameterizedType
、as suggested by IonSpin。
【讨论】:
但是他是硬编码的类(MyClass.class)我想通过动态的类也可以是 ListClass
对象为List<MyClass>
,这样的事情不存在。 List<MyClass>
可以有一个 ParameterizedType
,但您要么必须自己构建它,要么使用类型标记技巧。
@VelNaga 该解决方案失败的原因与您的尝试相同,List<MyClass>
不存在。由于他们的方法接受Class
参数,他们只能“嵌套”一个类型参数。
@VelNaga TypeToken
in Gson,TypeReference
in Jackson,ParameterizedTypeReference
in RestTemplate
,都是一样的模式,叫做type token。【参考方案8】:
正如下面的代码所示,它可以工作。
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();
【讨论】:
您的代码需要说明。请查看 SO 关于如何回答的政策。 事实上,这是迄今为止我见过的最好/最简单的答案。您只需要使用所示的 MyParameterizedTypeImpl 并且不需要具有实际类型和类型包装器的静态 Map。当然类必须传入方法或构造函数。 嗨,感谢您的回答,它适用于 CustomObjectClass<T>
参数这一事实将解决方案限制为非泛型类型。
RestTemplate java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.reflect.ParameterizedType
出现以下错误【参考方案9】:
注意:此答案引用/添加到 Sotirios Delimanolis 的答案和评论。
我试图让它与 Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>>
一起工作,正如 Sotirios 的评论中所指出的那样,但不能没有一个例子。
最后,我从 ParameterizedTypeReference 中删除了通配符和参数化,改用原始类型,就像这样
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);
这终于奏效了。
如果有人有参数化的示例,我将非常感激。
【讨论】:
【参考方案10】:我有另一种方法可以做到这一点...假设您将消息转换器换成 String 用于 RestTemplate,那么您可以接收原始 JSON。使用原始 JSON,您可以使用 Jackson 对象映射器将其映射到您的通用集合中。方法如下:
换掉消息转换器:
List<HttpMessageConverter<?>> oldConverters = new ArrayList<HttpMessageConverter<?>>();
oldConverters.addAll(template.getMessageConverters());
List<HttpMessageConverter<?>> stringConverter = new ArrayList<HttpMessageConverter<?>>();
stringConverter.add(new StringHttpMessageConverter());
template.setMessageConverters(stringConverter);
然后像这样得到你的 JSON 响应:
ResponseEntity<String> response = template.exchange(uri, HttpMethod.GET, null, String.class);
像这样处理响应:
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);
...
【讨论】:
我最喜欢这个解决方案。但什么是“clazz”?我以某种方式猜测它是T,但我无法表达它...... ObjectMapper 工厂的constructCollectionType 方法有两个输入,第一个是Collection 类,第二个(我标记为“clazz”)是上述Collection 类中包含的对象的类。 【参考方案11】:正如 Sotirios 解释的那样,您不能使用 ParameterizedTypeReference
,但 ParameterizedTypeReference 仅用于将 Type
提供给对象映射器,并且由于您拥有在类型擦除发生时被删除的类,您可以创建自己的ParameterizedType
并将其传递给RestTemplate
,以便对象映射器可以重建您需要的对象。
首先你需要实现 ParameterizedType 接口,你可以在 Google Gson 项目here 中找到一个实现。
将实现添加到项目后,您可以像这样扩展抽象 ParameterizedTypeReference
:
class FakeParameterizedTypeReference<T> extends ParameterizedTypeReference<T>
@Override
public Type getType()
Type [] responseWrapperActualTypes = MyClass.class;
ParameterizedType responseWrapperType = new ParameterizedTypeImpl(
ResponseWrapper.class,
responseWrapperActualTypes,
null
);
return responseWrapperType;
然后你可以将它传递给你的交换函数:
template.exchange(
uri,
HttpMethod.POST,
null,
new FakeParameterizedTypeReference<ResponseWrapper<T>>());
使用所有类型信息,对象映射器将正确构造您的 ResponseWrapper<MyClass>
对象
【讨论】:
JDK 中的 ParameterizedTypeImpl 类对其构造函数具有私有访问权限,并且没有设置器。您为此使用过图书馆吗?如果没有反射,我看不到任何实例化该类的方法...... 我从 Google Gson 项目中复制了 ParametrizedTypeImpl 实现,链接在答案中,同样在这里code.google.com/p/google-gson/source/browse/trunk/gson/src/main/… :) 啊,对不起,你完全正确。非常感谢! :) 此外,JDK 中的 ParameterizedTypeImpl 类具有私有构造函数,因为它旨在用作单例。它的 API 有一个 make 方法,您可以使用它来创建 ParameterizedType。以上是关于在具有泛型参数的泛型方法中使用 Spring RestTemplate的主要内容,如果未能解决你的问题,请参考以下文章
GetMethod 的 Type[] 以获取具有两个参数和一个类型参数的泛型方法
TypeScript 中具有泛型类型参数的泛型类型的替代方案