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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 11:51:24  来源:igfitidea点击:

Using Spring RestTemplate in generic method with generic parameter

javaspringgenericsHymansonresttemplate

提问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 Mapwith Classkey (Proposed by @Sotirios in the end of his answer). Would someone mind to give an example?

更新 1感谢@Sotirios,我理解了这个概念。不幸的是,我是在这里新注册的,所以我不能评论他的回答,所以写在这里。我不确定我是否清楚地了解如何实施建议的方法来解决我的问题MapClass密钥(由@Sotirios 在他的回答末尾提出)。有人介意举个例子吗?

采纳答案by Sotirios Delimanolis

No, it is not a bug. It is a result of how the ParameterizedTypeReferencehack 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 Typeobject 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 Typefor ResponseWrapper<MyClass>.

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

If you use

如果你使用

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

it will accurately return a Typefor ResponseWrapper<T>because that is how it appears in the source code.

它会准确地返回一个TypeforResponseWrapper<T>因为它在源代码中是这样出现的。

When Spring sees T, which is actually a TypeVariableobject, it doesn't know the type to use, so it uses its default.

当 Spring 看到T,它实际上是一个TypeVariable对象时,它不知道要使用的类型,所以它使用它的默认值。

You cannot use ParameterizedTypeReferencethe way you are proposing, making it generic in the sense of accepting any type. Consider writing a Mapwith key Classmapped to a predefined ParameterizedTypeReferencefor that class.

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

You can subclass ParameterizedTypeReferenceand override its getTypemethod 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 Typeto the object mapper, and as you have the class that is removed when type erasure happens, you can create your own ParameterizedTypeand 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 ParameterizedTypeReferencelike 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 ParameterizedTypeReferenceand not TypeToken. But we can create ParameterizedTypeReferenceimplementation which is adapter to TypeTokenitself.

这里唯一的缺点是许多 Spring API 需要ParameterizedTypeReference而不是TypeToken. 但是我们可以创建ParameterizedTypeReferenceTypeToken自身适配的实现。

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