在具有泛型参数的泛型方法中使用 Spring RestTemplate

要在 SpringRestTemplate 中使用泛型类型,我们需要使用 ParameterizedTypeReference(无法获取泛型 ResponseEntity < T > ,其中 T 是泛型类“ Some Class < Some GenericType >”)

假设我有一些类

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 错误吗?

多亏了@Sotirios,我才明白了这个概念。不幸的是,我是新注册的,所以我不能评论他的回答,所以写在这里。我不确定我是否清楚地理解如何实现提议的方法来解决我的 MapClass键的问题(提议由@Sotirios 在他的答案的末尾)。有人能举个例子吗?

143394 次浏览

不,这不是一个错误。这是如何 ParameterizedTypeReference黑客工作的结果。

如果你看它的实现,它使用的 Class#getGenericSuperclass()状态

返回表示实体的直接超类的 Type (类、接口、基元类型或 void)。

如果超类是参数化类型,则返回 < strong > Type对象 必须准确地反映源中使用的实际类型参数 密码

所以,如果你用

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

它会为 ResponseWrapper<MyClass>准确地返回一个 Type

如果你用

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

它将准确地返回 ResponseWrapper<T>Type,因为它在源代码中就是这样显示的。

当 Spring 看到实际上是 TypeVariable对象的 T时,它不知道要使用的类型,因此使用默认类型。

您不能按照您提议的方式使用 ParameterizedTypeReference,使其在接受任何类型的意义上具有通用性。考虑编写一个 Map,其键 Class映射到该类的预定义 ParameterizedTypeReference

您可以子类 ParameterizedTypeReference并重写它的 getType方法以返回适当创建的 ParameterizedType就像 IonSpin 建议的那样

正如 Sotirios 解释的那样,您不能使用 ParameterizedTypeReference,但参数化 TypeReference 仅用于向对象映射器提供 Type,并且当类型擦除发生时,您有被删除的类,因此您可以创建自己的 ParameterizedType并将其传递给 RestTemplate,以便对象映射器可以重新构造您需要的对象。

首先,您需要实现 Parameter terizedType 接口,您可以在 GoogleGson 项目 给你中找到一个实现。 一旦您将实现添加到您的项目中,您就可以像下面这样扩展抽象 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>对象

我还有另一种方法来做到这一点... 假设您将消息转换器替换为 RestTemplate 的 String,那么您可以接收原始 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);
}
...

注: 此答案参考/补充了索蒂里奥斯 · 德里马诺利斯的答案和评论。

我试图让它与 Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>>一起工作,正如 Sotirios 的评论所指出的那样,但是没有一个例子就做不到。

最后,我从 Parameter terizedTypeReference 中删除了通配符和参数化,改为使用原始类型,如下所示

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

终于成功了。

如果任何人有一个参数化的例子,我会非常感激看到它。

如下面的代码所示,它可以工作。

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


}

实际上,您可以这样做,但需要额外的代码。

番石榴等价于 参数化类型引用,它被称为 TypeToken

番石榴的类比 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>>

这里唯一的缺点是许多 SpringAPI 需要 ParameterizedTypeReference而不是 TypeToken。但是我们可以创建适配器到 TypeToken本身的 ParameterizedTypeReference实现。

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

我使用 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 > > 之类的东西)

我自己的通用 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。

我觉得有一个更简单的方法来做到这一点... 只需定义一个类的类型参数,你想要的。例如:


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

我发现这是一个更优雅的解决方案:

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

例如,给定以下 BaseResponseResponseData类:

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

}
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