Java 在具有泛型参数的泛型方法中使用 Spring RestTemplate
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21987295/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using Spring RestTemplate in generic method with generic parameter
提问by Artyom Kozhemiakin
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>")
要将泛型类型与 Spring RestTemplate 一起使用,我们需要使用ParameterizedTypeReference
(无法获得泛型 ResponseEntity<T> 其中 T 是泛型类“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.
...而不是获取ResponseEntity<ResponseWrapper<MyClass>>
对象,我正在获取ResponseEntity<ResponseWrapper<LinkedHashSet>>
对象。
How can I solve this problem? Is it a RestTemplate bug?
我怎么解决这个问题?这是一个 RestTemplate 错误吗?
UPDATE 1Thanks 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?
更新 1感谢@Sotirios,我理解了这个概念。不幸的是,我是在这里新注册的,所以我不能评论他的回答,所以写在这里。我不确定我是否清楚地了解如何实施建议的方法来解决我的问题Map
与Class
密钥(由@Sotirios 在他的回答末尾提出)。有人介意举个例子吗?
采纳答案by Sotirios Delimanolis
No, it is not a bug. It is a result of how the ParameterizedTypeReference
hack works.
不,这不是错误。这是ParameterizedTypeReference
黑客如何运作的结果。
If you look at its implementation, it uses Class#getGenericSuperclass()
which states
如果您查看它的实现,它会使用Class#getGenericSuperclass()
哪些状态
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.
返回表示由此类表示的实体(类、接口、原始类型或 void)的直接超类的类型。
如果超类是参数化类型,则
Type
返回的对象必须准确反映源代码中使用的实际类型参数。
So, if you use
所以,如果你使用
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}
it will accurately return a Type
for ResponseWrapper<MyClass>
.
它会准确地返回一个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.
它会准确地返回一个Type
forResponseWrapper<T>
因为它在源代码中是这样出现的。
When Spring sees T
, which is actually a TypeVariable
object, it doesn't know the type to use, so it uses its default.
当 Spring 看到T
,它实际上是一个TypeVariable
对象时,它不知道要使用的类型,所以它使用它的默认值。
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.
您不能使用ParameterizedTypeReference
您提议的方式,使其在接受任何类型的意义上通用。考虑编写一个Map
键Class
映射到ParameterizedTypeReference
该类的预定义的键。
You can subclass ParameterizedTypeReference
and override its getType
method to return an appropriately created ParameterizedType
, as suggested by IonSpin.
您可以子类化ParameterizedTypeReference
并覆盖其getType
方法以返回适当创建的方法ParameterizedType
,如 IonSpin 所建议的那样。
回答by IonSpin
As Sotirios explains, you can not use the ParameterizedTypeReference
, but ParameterizedTypeReference is used only to provide Type
to the object mapper, and as you have the class that is removed when type erasure happens, you can create your own ParameterizedType
and pass that to RestTemplate
, so that the object mapper can reconstruct the object you need.
正如 Sotirios 解释的那样,您不能使用ParameterizedTypeReference
,但 ParameterizedTypeReference 仅用于提供Type
给对象映射器,并且由于您拥有在发生类型擦除时被删除的类,您可以创建自己的类ParameterizedType
并将其传递给RestTemplate
,以便对象mapper 可以重建你需要的对象。
First you need to have the ParameterizedType interface implemented, you can find an implementation in Google Gson project here.
Once you add the implementation to your project, you can extend the abstract ParameterizedTypeReference
like this:
首先你需要实现 ParameterizedType 接口,你可以在这里找到谷歌 Gson 项目中的实现。将实现添加到项目后,您可以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;
}
}
And then you can pass that to your exchange function:
然后你可以将它传递给你的交换函数:
template.exchange(
uri,
HttpMethod.POST,
null,
new FakeParameterizedTypeReference<ResponseWrapper<T>>());
With all the type information present object mapper will properly construct your ResponseWrapper<MyClass>
object
使用所有类型信息,对象映射器将正确构造您的ResponseWrapper<MyClass>
对象
回答by Dilettante44
I have another way to do this... suppose you swap out your message converter to String for your RestTemplate, then you can receive raw JSON. Using the raw JSON, you can then map it into your Generic Collection using a Hymanson Object Mapper. Here's how:
我有另一种方法来做到这一点......假设你将你的消息转换器换成你的 RestTemplate 的字符串,那么你可以接收原始 JSON。使用原始 JSON,然后您可以使用 Hymanson 对象映射器将其映射到您的通用集合中。就是这样:
Swap out the message converter:
换出消息转换器:
List<HttpMessageConverter<?>> oldConverters = new ArrayList<HttpMessageConverter<?>>();
oldConverters.addAll(template.getMessageConverters());
List<HttpMessageConverter<?>> stringConverter = new ArrayList<HttpMessageConverter<?>>();
stringConverter.add(new StringHttpMessageConverter());
template.setMessageConverters(stringConverter);
Then get your JSON response like this:
然后像这样得到你的 JSON 响应:
ResponseEntity<String> response = template.exchange(uri, HttpMethod.GET, null, String.class);
Process the response like this:
像这样处理响应:
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);
}
...
回答by Ginkobonsai
Note: This answer refers/adds to Sotirios Delimanolis's answer and comment.
注意:此答案是指/添加到 Sotirios Delimanolis 的答案和评论中。
I tried to get it to work with Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>>
, as indicated in Sotirios's comment, but couldn't without an example.
我试图让它与 一起工作Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>>
,如 Sotirios 的评论所示,但不能没有例子。
In the end, I dropped the wildcard and parametrisation from ParameterizedTypeReference and used raw types instead, like so
最后,我从 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);
and this finally worked.
这终于奏效了。
If anyone has an example with parametrisation, I'd be very grateful to see it.
如果有人有参数化的例子,我会很感激看到它。
回答by ???
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();
}
}
回答by Ruslan Stelmachenko
Actually, you can do this, but with additional code.
实际上,您可以这样做,但需要额外的代码。
There is Guavaequivalent of ParameterizedTypeReferenceand it's called TypeToken.
有相当于ParameterizedTypeReference 的番石榴,它被称为TypeToken。
Guava's class is much more powerful then Spring's equivalent. You can compose the TypeTokens as you wish. For example:
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);
}
If you call mapToken(TypeToken.of(String.class), TypeToken.of(BigInteger.class));
you will create TypeToken<Map<String, BigInteger>>
!
如果你打电话,mapToken(TypeToken.of(String.class), TypeToken.of(BigInteger.class));
你会创造TypeToken<Map<String, BigInteger>>
!
The only disadvantage here is that many Spring APIs require ParameterizedTypeReference
and not TypeToken
. But we can create ParameterizedTypeReference
implementation which is adapter to TypeToken
itself.
这里唯一的缺点是许多 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 + ">";
}
}
}
Then you can use it like this:
然后你可以像这样使用它:
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;
}
And call it like:
并称之为:
ResponseWrapper<MyClass> result = makeRequest(uri, MyClass.class);
And the response body will be correctly deserialized as ResponseWrapper<MyClass>
!
并且响应主体将被正确反序列化为ResponseWrapper<MyClass>
!
You can even use more complex types if you rewrite your generic request method (or overload it) like this:
如果您像这样重写通用请求方法(或重载它),您甚至可以使用更复杂的类型:
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;
}
This way T
can be complex type, like List<MyClass>
.
这种方式T
可以是复杂类型,例如List<MyClass>
.
And call it like:
并称之为:
ResponseWrapper<List<MyClass>> result = makeRequest(uri, new TypeToken<List<MyClass>>() {});
回答by Justin
I am using org.springframework.core.ResolvableTypefor a ListResultEntity :
我将org.springframework.core.ResolvableType用于 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 )
这只使用 spring 并且当然需要一些关于返回类型的知识(但甚至应该适用于 Wrapper>> 之类的东西,只要你提供类作为 varargs )
回答by AlexV
My own implementation of generic restTemplate call:
我自己的通用 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 Hymanson and String message converters
restTemplate.getMessageConverters().add(new MappingHymanson2HttpMessageConverter());
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;
}
To add some context, I'm consuming RESTful service with this, hence all requests and responses are wrapped into small POJO like this:
为了添加一些上下文,我正在使用 RESTful 服务,因此所有请求和响应都被包装到这样的小 POJO 中:
public class ValidateRequest {
User user;
User checkedUser;
Vehicle vehicle;
}
and
和
public class UserResponse {
User user;
RequestResult requestResult;
}
Method which calls this is the following:
调用它的方法如下:
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();
}
And yes, there's a List dto-s as well.
是的,还有一个 List dto-s。
回答by byronaltice
I feel like there's a much easier way to do this... Just define a class with the type parameters that you want. e.g.:
我觉得有一种更简单的方法可以做到这一点......只需定义一个具有您想要的类型参数的类。例如:
final class MyClassWrappedByResponse extends ResponseWrapper<MyClass> {
private static final long serialVersionUID = 1L;
}
Now change your code above to this and it should work:
现在将上面的代码更改为此,它应该可以工作:
public ResponseWrapper<MyClass> makeRequest(URI uri) {
ResponseEntity<MyClassWrappedByResponse> response = template.exchange(
uri,
HttpMethod.POST,
null,
MyClassWrappedByResponse.class
return response;
}