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

Art*_*kin 53 java generics spring jackson resttemplate

要使用Spring RestTemplate的泛型类型,我们需要使用ParameterizedTypeReference(无法获得通用的ResponseEntity <T>,其中T是泛型类"SomeClass <SomeGenericType>")

假设我有一些课

public class MyClass {
    int users[];

    public int[] getUsers() { return users; }
    public void setUsers(int[] users) {this.users = users;}
}
Run Code Online (Sandbox Code Playgroud)

还有一些包装类

public class ResponseWrapper <T> {
    T response;

    public T getResponse () { return response; }
    public void setResponse(T response) {this.response = response;}
}
Run Code Online (Sandbox Code Playgroud)

所以如果我想做这样的事情,一切都好.

public ResponseWrapper<MyClass> makeRequest(URI uri) {
    ResponseEntity<ResponseWrapper<MyClass>> response = template.exchange(
        uri,
        HttpMethod.POST,
        null,
        new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {});
    return response;
}
Run Code Online (Sandbox Code Playgroud)

但是当我试图创建上述方法的通用变体时......

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;
}
Run Code Online (Sandbox Code Playgroud)

......并且像这样调用这个方法......

makeRequest(uri, MyClass.class)
Run Code Online (Sandbox Code Playgroud)

...而不是获得ResponseEntity<ResponseWrapper<MyClass>>对象我得到了ResponseEntity<ResponseWrapper<LinkedHashSet>>对象.

我怎么解决这个问题?它是RestTemplate的错误吗?

更新1 感谢@Sotirios我理解这个概念.不幸的是我刚刚在这里注册,所以我不能评论他的答案,所以写在这里.我不确定我是否清楚地了解如何Map使用Class密钥来解决我的问题(在答案的最后由@Sotirios提出).有人会介意举个例子吗?

Sot*_*lis 48

不,这不是一个bug.这是ParameterizedTypeReference黑客行为的结果.

如果你看一下它的实现,它会使用Class#getGenericSuperclass()哪些状态

返回表示此Class表示的实体(类,接口,基本类型或void)的直接超类的Type.

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

所以,如果你使用

new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}
Run Code Online (Sandbox Code Playgroud)

它会准确地返回TypeResponseWrapper<MyClass>.

如果你使用

new ParameterizedTypeReference<ResponseWrapper<T>>() {}
Run Code Online (Sandbox Code Playgroud)

它将准确地返回Typefor,ResponseWrapper<T>因为它就是它在源代码中的显示方式.

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

你无法使用ParameterizedTypeReference你提出的方式,在接受任何类型的意义上使它成为通用的.考虑Map使用Class映射到ParameterizedTypeReference该类的预定义的键来编写.

你也可以继承ParameterizedTypeReference并覆盖其getType方法返回一个适当的创建ParameterizedType,通过IonSpin的建议.


김원겸*_*김원겸 16

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

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

}
Run Code Online (Sandbox Code Playgroud)

  • 您的代码需要解释.请查看SO关于如何回答的政策. (2认同)
  • 该答案的解释不充分,解决方案有限。使用`Class &lt;T&gt;`参数的事实将解决方案限制为非泛型类型。 (2认同)

Ion*_*pin 9

正如Sotirios所解释的那样,你不能使用ParameterizedTypeReference,但是ParameterizedTypeReference仅用于提供Type给对象映射器,并且当你有类型擦除发生时被删除的类时,你可以创建自己的类ParameterizedType并将其传递给RestTemplate,以便对象mapper可以重建您需要的对象.

首先,您需要实现ParameterizedType接口,您可以在此处找到Google 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以将它传递给你的交换函数:

template.exchange(
    uri,
    HttpMethod.POST,
    null,
    new FakeParameterizedTypeReference<ResponseWrapper<T>>());
Run Code Online (Sandbox Code Playgroud)

使用所有类型信息,对象映射器将正确构造您的ResponseWrapper<MyClass>对象


Rus*_*nko 8

实际上,您可以执行此操作,但需要附加代码。

番石榴相当于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);
}
Run Code Online (Sandbox Code Playgroud)

如果您致电,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 + ">";
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

然后,您可以像这样使用它:

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;
}
Run Code Online (Sandbox Code Playgroud)

并这样称呼:

ResponseWrapper<MyClass> result = makeRequest(uri, MyClass.class);
Run Code Online (Sandbox Code Playgroud)

并且响应正文将正确反序列化为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;
}
Run Code Online (Sandbox Code Playgroud)

这种方式T可以是复杂类型,例如List<MyClass>

并这样称呼:

ResponseWrapper<List<MyClass>> result = makeRequest(uri, new TypeToken<List<MyClass>>() {});
Run Code Online (Sandbox Code Playgroud)


小智 7

我正在将org.springframework.core.ResolvableType用于ListResultEntity:

    ResolvableType resolvableType = ResolvableType.forClassWithGenerics(ListResultEntity.class, itemClass);
    ParameterizedTypeReference<ListResultEntity<T>> typeRef = ParameterizedTypeReference.forType(resolvableType.getType());
Run Code Online (Sandbox Code Playgroud)

因此,在您的情况下:

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;
}
Run Code Online (Sandbox Code Playgroud)

这仅利用spring,并且当然需要有关返回类型的一些知识(但是,即使您将类提供为varargs,它也应该适用于Wrapper >>之类的东西)

  • 我想你的意思是 ResolvableType.forClassWithGenerics(ResponseWrapper.class, clazz).getType() ,对吧?您的帖子在我的项目中有效,我认为这是最短和最简单的帖子。谢谢贾斯汀! (2认同)