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

时间:2014-02-24 12:05:35

标签: java spring generics jackson resttemplate

要在Spring RestTemplate中使用泛型类型,我们需要使用ParameterizedTypeReferenceUnable 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在答案的最后提出)。有人会介绍一个例子吗?

11 个答案:

答案 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方法,以返回已正确创建的ParameterizedTypeas 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 ) );
}

例如,给定以下 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()  );
    
}

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