【问题标题】:Using Spring RestTemplate in generic method with generic parameter在具有泛型参数的泛型方法中使用 Spring RestTemplate
【发布时间】:2014-03-26 02:06:52
【问题描述】:

要在 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&lt;ResponseWrapper&lt;MyClass&gt;&gt; 对象我得到ResponseEntity&lt;ResponseWrapper&lt;LinkedHashSet&gt;&gt; 对象。

我该如何解决这个问题?是 RestTemplate 错误吗?

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

【问题讨论】:

  • 您应该始终能够评论您的问题的答案。建议的解决方案是这样工作的。您创建一个Map&lt;Class, ParameterizedTypeReference&lt;ResponseWrapper&lt;?&gt;&gt;&gt;。您将预构建的 ParameterizedTypeReference 实例添加到您期望的实际类型的 Map 对应的 Class
  • @SotiriosDelimanolis - 你能为刚接触 Java 的人改写一下吗?我来自 C# 背景,所以这对我来说是非常有趣的经历。

标签: java spring generics jackson resttemplate


【解决方案1】:
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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      我觉得有一种更简单的方法可以做到这一点......只需使用您想要的类型参数定义一个类。例如:

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          我正在使用 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,当然需要一些关于返回类型的知识(但甚至应该适用于 Wrapper>> 之类的东西,只要你将类作为 varargs 提供)

          【讨论】:

          • 我想你的意思是 ResolvableType.forClassWithGenerics(ResponseWrapper.class, clazz).getType() ,对吧?您的帖子正在我的项目中工作,我认为它是最短和最简单的。谢谢贾斯汀!
          • @Blangero 我收到 java.lang.IllegalArgumentException:为您的评论指定的泛型数量不匹配。
          【解决方案6】:

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

          Guava 等同于 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);
          }
          

          如果您调用mapToken(TypeToken.of(String.class), TypeToken.of(BigInteger.class));,您将创建TypeToken&lt;Map&lt;String, BigInteger&gt;&gt;

          这里唯一的缺点是许多 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&lt;MyClass&gt;

          如果您像这样重写您的通用请求方法(或重载它),您甚至可以使用更复杂的类型:

          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&lt;MyClass&gt;

          然后这样称呼它:

          ResponseWrapper<List<MyClass>> result = makeRequest(uri, new TypeToken<List<MyClass>>() {});
          

          【讨论】:

          • 这适用于我的用例,这是一种使用 GitHub 样式的链接头解析分页数据的通用机制。它为我提供了一种创建有效类型以传递给 Spring HttpMessageConverterExtractor 的方法,让我可以将接口与我的类保持通用。
          • 多么棒的解决方案!像魅力一样工作。
          • 从 Spring 4.3.12 开始,您可以使用ParameterizedTypeReference.forType(typeToken.getType())
          【解决方案7】:

          不,这不是错误。这是ParameterizedTypeReference hack 工作方式的结果。

          如果你查看它的实现,它使用Class#getGenericSuperclass() 声明

          返回表示实体的直接超类的类型 (类、接口、原始类型或 void)由该类表示。

          如果超类是参数化类型,返回Type对象 必须准确反映源中使用的实际类型参数 代码。

          所以,如果你使用

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

          它将准确地为ResponseWrapper&lt;MyClass&gt; 返回一个Type

          如果你使用

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

          它将准确地为ResponseWrapper&lt;T&gt; 返回一个Type,因为它在源代码中就是这样显示的。

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

          你不能像你提议的那样使用ParameterizedTypeReference,让它在接受任何类型的意义上都是通用的。考虑编写一个 Map,其中键 Class 映射到该类的预定义 ParameterizedTypeReference

          您可以继承ParameterizedTypeReference 并覆盖其getType 方法以返回适当创建的ParameterizedTypeas suggested by IonSpin

          【讨论】:

          • 但是他是硬编码的类({MyClass.class})我想通过动态的类也可以是 List 在我的情况下......你的建议很感激。
          • 这个答案的缺点是什么(stackoverflow.com/a/41629503/3656056)它适用于 CustomObject 但对于 CustomObject> 失败
          • @VelNaga 没有Class 对象为List&lt;MyClass&gt;,这样的事情不存在。 List&lt;MyClass&gt; 可以有一个 ParameterizedType,但您要么必须自己构建它,要么使用类型标记技巧。
          • @VelNaga 该解决方案失败的原因与您的尝试相同,List&lt;MyClass&gt; 不存在。由于他们的方法接受Class 参数,他们只能“嵌套”一个类型参数。
          • @VelNaga TypeToken in Gson,TypeReference in Jackson,ParameterizedTypeReference in RestTemplate,都是一样的模式,叫做type token
          【解决方案8】:

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

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

          【讨论】:

          • 您的代码需要说明。请查看 SO 关于如何回答的政策。
          • 事实上,这是迄今为止我见过的最好/最简单的答案。您只需要使用所示的 MyParameterizedTypeImpl 并且不需要具有实际类型和类型包装器的静态 Map。当然类必须传入方法或构造函数。
          • 嗨,感谢您的回答,它适用于 CustomObject,但它对 CustomObject> 失败,您能帮我解决 CustomObject>
          • 这个答案解释不清,解决方案有限。您使用Class&lt;T&gt; 参数这一事实将解决方案限制为非泛型类型。
          • RestTemplate java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.reflect.ParameterizedType 出现以下错误
          【解决方案9】:

          注意:此答案引用/添加到 Sotirios Delimanolis 的答案和评论。

          我试图让它与 Map&lt;Class, ParameterizedTypeReference&lt;ResponseWrapper&lt;?&gt;&gt;&gt; 一起工作,正如 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);
          

          这终于奏效了。

          如果有人有参数化的示例,我将非常感激。

          【讨论】:

            【解决方案10】:

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

            【讨论】:

            • 我最喜欢这个解决方案。但什么是“clazz”?我以某种方式猜测它是T,但我无法表达它......
            • ObjectMapper 工厂的constructCollectionType 方法有两个输入,第一个是Collection 类,第二个(我标记为“clazz”)是上述Collection 类中包含的对象的类。
            【解决方案11】:

            正如 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&lt;MyClass&gt; 对象

            【讨论】:

            • JDK 中的 ParameterizedTypeImpl 类对其构造函数具有私有访问权限,并且没有设置器。您为此使用过图书馆吗?如果没有反射,我看不到任何实例化该类的方法......
            • 我从 Google Gson 项目中复制了 ParametrizedTypeImpl 实现,链接在答案中,同样在这里code.google.com/p/google-gson/source/browse/trunk/gson/src/main/… :)
            • 啊,对不起,你完全正确。非常感谢! :)
            • 此外,JDK 中的 ParameterizedTypeImpl 类具有私有构造函数,因为它旨在用作单例。它的 API 有一个 make 方法,您可以使用它来创建 ParameterizedType。
            猜你喜欢
            • 2011-12-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多