要在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
来解决我的问题,并使用{{1}}密钥(@Sotirios在答案的最后提出)。有人会介绍一个例子吗?
答案 0 :(得分:42)
不,这不是一个错误。这是ParameterizedTypeReference
hack如何工作的结果。
如果你看一下它的实现,就会使用Class#getGenericSuperclass()
表示
返回表示实体的直接超类的Type (类,接口,基本类型或void)由此类表示。
如果超类是参数化类型,则返回
Type
个对象 必须准确反映源中使用的实际类型参数 代码。强>
所以,如果你使用
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}
它会准确地为Type
返回ResponseWrapper<MyClass>
。
如果您使用
new ParameterizedTypeReference<ResponseWrapper<T>>() {}
它会准确地为Type
返回ResponseWrapper<T>
,因为这就是它在源代码中的显示方式。
当Spring看到T
(实际上是TypeVariable
个对象时,它不知道要使用的类型,因此它使用默认值。
您不能按照提议的方式使用ParameterizedTypeReference
,在接受任何类型的意义上使其成为通用的。考虑使用映射到该类的预定义Map
的密钥Class
编写ParameterizedTypeReference
。
您可以继承ParameterizedTypeReference
并覆盖其getType
方法,以返回已正确创建的ParameterizedType
,as suggested by IonSpin。
答案 1 :(得分:10)
如下面的代码所示,它可以正常工作。
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();
}
}
答案 2 :(得分:9)
正如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>
对象
答案 3 :(得分:5)
实际上,您可以执行此操作,但需要附加代码。
Guava相当于ParameterizedTypeReference,它被称为TypeToken。
番石榴的课程比春天的课程强大得多。 您可以根据需要编写TypeTokens。 例如:
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>>() {});
答案 4 :(得分:2)
我有另一种方法可以做到这一点...假设您将消息转换器换成了RestTemplate的String,那么您可以接收原始JSON。使用原始JSON,您可以使用Jackson Object Mapper将其映射到您的Generic Collection中。方法如下:
交换消息转换器:
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);
}
...
答案 5 :(得分:2)
我正在将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,并且当然需要有关返回类型的一些知识(但只要您将类提供为varargs,它甚至应该适用于Wrapper >>之类的东西
答案 6 :(得分:1)
我自己的通用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();
}
是的,还有一个列表dto-s。
答案 7 :(得分:0)
注意:此答案引用/添加了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);
这终于奏效了。
如果有人有参数化的例子,我会非常感激看到它。
答案 8 :(得分:0)
我觉得有一种更简单的方法可以执行此操作...只需使用所需的类型参数定义一个类。例如:
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;
}
答案 9 :(得分:0)
我发现这是一个更优雅的解决方案:
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() );
}
答案 10 :(得分:-1)
你可以在不使用包装类本身的情况下完成它。如果泛型
,尝试使用真正的力量/**
*
* 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;
}