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;}
}
そして、いくつかのラッパークラス
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のバグですか?
UPDATE 1@Sotiriosのおかげで、この概念を理解しました。残念ながら、私はここに新しく登録されたので、彼の答えにコメントできないので、ここに書いてください。 Map
キーでClass
キー(彼の答えの最後に@Sotiriosによって提案された)で私の問題を解決するために提案されたアプローチを実装する方法を明確に理解しているかどうかはわかりません。例を挙げてくれませんか?
いいえ、それはバグではありません。これは、ParameterizedTypeReference
ハックの仕組みの結果です。
その実装を見ると、 Class#getGenericSuperclass()
を使用しています
このクラスによって表されるエンティティ(クラス、インターフェース、プリミティブ型、またはvoid)の直接のスーパークラスを表すTypeを返します。
スーパークラスがパラメーター化された型の場合、返される
Type
オブジェクトは、ソースコードで使用される実際の型パラメーターを正確に反映する必要があります。
したがって、使用する場合
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}
ResponseWrapper<MyClass>
に対してType
を正確に返します。
使用する場合
new ParameterizedTypeReference<ResponseWrapper<T>>() {}
ResponseWrapper<T>
に対してType
を正確に返します。これがソースコードでの表示方法だからです。
Springが実際にT
オブジェクトであるTypeVariable
を検出すると、使用するタイプがわからないため、デフォルトが使用されます。
ParameterizedTypeReference
を提案された方法で使用することはできません。あらゆるタイプを受け入れるという意味で汎用的にすることはできません。そのクラスの事前定義されたMap
にマップされたClass
キーを持つParameterizedTypeReference
を書くことを検討してください。
ParameterizedTypeReference
をサブクラス化し、getType
メソッドをオーバーライドして、適切に作成されたParameterizedType
、 IonSpinの提案どおり を返すことができます。
以下のコードが示すように、動作します。
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();
}
}
Sotiriosが説明するように、ParameterizedTypeReference
は使用できませんが、ParameterizedTypeReferenceはオブジェクトマッパーにType
を提供するためにのみ使用され、型消去が発生すると削除されるクラスがあるので、独自の ParameterizedType
を作成できます=そしてそれをRestTemplate
に渡すと、オブジェクトマッパーが必要なオブジェクトを再構築できるようになります。
最初に、ParameterizedTypeインターフェイスを実装する必要があります。GoogleGsonプロジェクトで実装を見つけることができます 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>
オブジェクトを適切に構築します
実際には、これを行うことができますが、追加のコードが必要です。
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<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>>() {});
ListResultEntityに org.springframework.core.ResolvableType を使用しています:
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;
}
これは春を利用するだけで、もちろん返される型についての知識が必要です(しかし、クラスをvarargsとして提供する限り、Wrapper >>のようなものでも動作するはずです)
これを行う別の方法があります... RestTemplateのメッセージコンバーターをStringにスワップすると、生のJSONを受信できるようになります。生のJSONを使用して、Jackson Object Mapperを使用してGenericコレクションにマップできます。方法は次のとおりです。
メッセージコンバーターを交換します。
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);
}
...
汎用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もあります。
注:この回答は、Sotirios Delimanolisの回答とコメントを参照/追加しています。
Sotiriosのコメントに示されているように、Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>>
で動作するように試みましたが、例なしではできませんでした。
最後に、ParameterizedTypeReferenceからワイルドカードとパラメーター化を削除し、代わりにrawタイプを使用しました。
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);
そして、これは最終的に機能しました。
誰かがパラメータ化の例を持っているなら、私はそれを見てとても感謝します。