Art*_*kin 53 java generics spring jackson resttemplate
要使用Spring RestTemplate的泛型类型,我们需要使用ParameterizedTypeReference
(无法获得通用的ResponseEntity <T>,其中T是泛型类"SomeClass <SomeGenericType>")
假设我有一些课
public class MyClass {
int users[];
public int[] getUsers() { return users; }
public void setUsers(int[] users) {this.users = users;}
}
Run Code Online (Sandbox Code Playgroud)
还有一些包装类
public class ResponseWrapper <T> {
T response;
public T getResponse () { return response; }
public void setResponse(T response) {this.response = response;}
}
Run Code Online (Sandbox Code Playgroud)
所以如果我想做这样的事情,一切都好.
public ResponseWrapper<MyClass> makeRequest(URI uri) {
ResponseEntity<ResponseWrapper<MyClass>> response = template.exchange(
uri,
HttpMethod.POST,
null,
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {});
return response;
}
Run Code Online (Sandbox Code Playgroud)
但是当我试图创建上述方法的通用变体时......
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;
}
Run Code Online (Sandbox Code Playgroud)
......并且像这样调用这个方法......
makeRequest(uri, MyClass.class)
Run Code Online (Sandbox Code Playgroud)
...而不是获得ResponseEntity<ResponseWrapper<MyClass>>
对象我得到了ResponseEntity<ResponseWrapper<LinkedHashSet>>
对象.
我怎么解决这个问题?它是RestTemplate的错误吗?
更新1
感谢@Sotirios我理解这个概念.不幸的是我刚刚在这里注册,所以我不能评论他的答案,所以写在这里.我不确定我是否清楚地了解如何Map
使用Class
密钥来解决我的问题(在答案的最后由@Sotirios提出).有人会介意举个例子吗?
Sot*_*lis 48
不,这不是一个bug.这是ParameterizedTypeReference
黑客行为的结果.
如果你看一下它的实现,它会使用Class#getGenericSuperclass()
哪些状态
返回表示此Class表示的实体(类,接口,基本类型或void)的直接超类的Type.
如果超类是参数化类型,则
Type
返回的对象必须准确反映源代码中使用的实际类型参数.
所以,如果你使用
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}
Run Code Online (Sandbox Code Playgroud)
它会准确地返回Type
了ResponseWrapper<MyClass>
.
如果你使用
new ParameterizedTypeReference<ResponseWrapper<T>>() {}
Run Code Online (Sandbox Code Playgroud)
它将准确地返回Type
for,ResponseWrapper<T>
因为它就是它在源代码中的显示方式.
当Spring看到T
它实际上是一个TypeVariable
对象时,它不知道要使用的类型,所以它使用它的默认值.
你无法使用ParameterizedTypeReference
你提出的方式,在接受任何类型的意义上使它成为通用的.考虑Map
使用Class
映射到ParameterizedTypeReference
该类的预定义的键来编写.
你也可以继承ParameterizedTypeReference
并覆盖其getType
方法返回一个适当的创建ParameterizedType
,通过IonSpin的建议.
김원겸*_*김원겸 16
如下面的代码所示,它可以工作.
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();
}
}
Run Code Online (Sandbox Code Playgroud)
正如Sotirios所解释的那样,你不能使用ParameterizedTypeReference
,但是ParameterizedTypeReference仅用于提供Type
给对象映射器,并且当你有类型擦除发生时被删除的类时,你可以创建自己的类ParameterizedType
并将其传递给RestTemplate
,以便对象mapper可以重建您需要的对象.
首先,您需要实现ParameterizedType接口,您可以在此处找到Google 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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后你可以将它传递给你的交换函数:
template.exchange(
uri,
HttpMethod.POST,
null,
new FakeParameterizedTypeReference<ResponseWrapper<T>>());
Run Code Online (Sandbox Code Playgroud)
使用所有类型信息,对象映射器将正确构造您的ResponseWrapper<MyClass>
对象
实际上,您可以执行此操作,但需要附加代码。
有番石榴相当于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);
}
Run Code Online (Sandbox Code Playgroud)
如果您致电,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 + ">";
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以像这样使用它:
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;
}
Run Code Online (Sandbox Code Playgroud)
并这样称呼:
ResponseWrapper<MyClass> result = makeRequest(uri, MyClass.class);
Run Code Online (Sandbox Code Playgroud)
并且响应正文将正确反序列化为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;
}
Run Code Online (Sandbox Code Playgroud)
这种方式T
可以是复杂类型,例如List<MyClass>
。
并这样称呼:
ResponseWrapper<List<MyClass>> result = makeRequest(uri, new TypeToken<List<MyClass>>() {});
Run Code Online (Sandbox Code Playgroud)
小智 7
我正在将org.springframework.core.ResolvableType用于ListResultEntity:
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(ListResultEntity.class, itemClass);
ParameterizedTypeReference<ListResultEntity<T>> typeRef = ParameterizedTypeReference.forType(resolvableType.getType());
Run Code Online (Sandbox Code Playgroud)
因此,在您的情况下:
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;
}
Run Code Online (Sandbox Code Playgroud)
这仅利用spring,并且当然需要有关返回类型的一些知识(但是,即使您将类提供为varargs,它也应该适用于Wrapper >>之类的东西)