[SPRING] 제네릭 매개 변수를 사용하는 제네릭 메서드에서 Spring RestTemplate 사용
SPRING제네릭 매개 변수를 사용하는 제네릭 메서드에서 Spring RestTemplate 사용
Spring RestTemplate으로 제네릭 타입을 사용하려면 ParameterizedTypeReference (일반 ResponseEntity
수업이 있다고 가정 해 봅시다.
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;}
}
그래서 내가 이런 식으로하려고하면 모든 것이 OK입니다.
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
이 문제를 어떻게 해결할 수 있습니까? RestTemplate 버그입니까?
업데이트 1 @Sotirios 덕분에 나는 개념을 이해한다. 불행하게도 나는 새로 등록 된 사람이다. 그래서 나는 그의 대답에 대해 코멘트를 할 수 없다. 나는 클래스 키를 가진 맵으로 내 문제를 해결하기 위해 제안 된 접근법을 구현하는 방법을 분명히 이해하지 못했다. (그의 대답 끝에 @Sotirios가 제안 함). 누군가 모범을 보이겠습니까?
해결법
-
==============================
1.아니, 버그가 아니야. 이는 ParameterizedTypeReference 해킹이 작동하는 방식의 결과입니다.
아니, 버그가 아니야. 이는 ParameterizedTypeReference 해킹이 작동하는 방식의 결과입니다.
그것의 구현을 보면 Class # getGenericSuperclass ()가 사용된다.
그래서, 만약 당신이
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}
ResponseWrapper
의 Type을 정확하게 반환합니다. 사용하는 경우
new ParameterizedTypeReference<ResponseWrapper<T>>() {}
그것이 ResponseWrapper
의 Type을 정확하게 리턴 할 것이기 때문에 ResponseWrapper가 소스 코드에 나타나는 방식입니다. Spring이 실제로 TypeVariable 객체 인 T를 볼 때, 사용할 타입을 알지 못하기 때문에 기본값을 사용합니다.
ParameterizedTypeReference는 제안하는 방식대로 사용할 수 없으므로 모든 유형을 받아 들일 수있는 일반적인 의미를 갖습니다. 해당 클래스에 대해 미리 정의 된 ParameterizedTypeReference에 매핑 된 키 Class를 사용하여 Map을 작성하는 것을 고려하십시오.
ParameterizedTypeReference를 서브 클래스 화해 getType 메소드를 오버라이드 (override) 해, IonSpin가 제안한대로, 적절하게 작성된 ParameterizedType를 돌려 줄 수가 있습니다.
-
==============================
2.Sotirios가 설명했듯이 ParameterizedTypeReference를 사용할 수는 없지만 ParameterizedTypeReference는 Type을 객체 매퍼에 제공하는 용도로만 사용되며 유형 소거가 발생할 때 제거되는 클래스가 있으므로 자신 만의 ParameterizedType을 만들어 RestTemplate에 전달할 수 있습니다. 오브젝트 매퍼는 필요한 오브젝트를 재구성 할 수 있습니다.
Sotirios가 설명했듯이 ParameterizedTypeReference를 사용할 수는 없지만 ParameterizedTypeReference는 Type을 객체 매퍼에 제공하는 용도로만 사용되며 유형 소거가 발생할 때 제거되는 클래스가 있으므로 자신 만의 ParameterizedType을 만들어 RestTemplate에 전달할 수 있습니다. 오브젝트 매퍼는 필요한 오브젝트를 재구성 할 수 있습니다.
먼저 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; } }
그런 다음 교환 기능에 전달할 수 있습니다.
template.exchange( uri, HttpMethod.POST, null, new FakeParameterizedTypeReference<ResponseWrapper<T>>());
모든 유형 정보가 있으면 개체 매퍼가 ResponseWrapper
개체를 올바르게 구성합니다. -
==============================
3.아래의 코드에서 보여 주듯이 작동합니다.
아래의 코드에서 보여 주듯이 작동합니다.
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(); } }
-
==============================
4.실제로이 작업을 수행 할 수 있지만 추가 코드가 필요합니다.
실제로이 작업을 수행 할 수 있지만 추가 코드가 필요합니다.
ParameterizedTypeReference에 해당하는 Guava가 있으며 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
여기서 유일한 단점은 많은 Spring API가 TypeToken이 아닌 ParameterizedTypeReference를 필요로한다는 것이다. 그러나 TypeToken 자체에 대한 Adapter 인 ParameterizedTypeReference 구현체를 생성 할 수 있습니다.
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
로 올바르게 deserialize됩니다! 다음과 같이 일반 요청 메소드를 재 작성하거나 오버로드하면 더 복잡한 유형을 사용할 수도 있습니다.
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; }
이 방법은 List
처럼 복잡한 유형이 될 수 있습니다. 그리고 그것을 다음과 같이 부르십시오.
ResponseWrapper<List<MyClass>> result = makeRequest(uri, new TypeToken<List<MyClass>>() {});
-
==============================
5.이 작업을 수행하는 또 다른 방법이 있습니다 ... RestTemplate 용 String으로 메시지 변환기를 바꾼 다음 원시 JSON을받을 수 있다고 가정합니다. 원시 JSON을 사용하여 Jackson Object Mapper를 사용하여 Generic Collection으로 매핑 할 수 있습니다. 방법은 다음과 같습니다.
이 작업을 수행하는 또 다른 방법이 있습니다 ... 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); } ...
-
==============================
6.참고 :이 답변은 Sotirios Delimanolis의 대답과 의견을 언급 / 추가합니다.
참고 :이 답변은 Sotirios Delimanolis의 대답과 의견을 언급 / 추가합니다.
나는 Map
>>, 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);
이것은 결국 효과가있었습니다.
누군가가 매개 변수 화를 가진 예제를 가지고 있다면, 나는 그것을 매우 감사하게 여길 것이다.
-
==============================
7.래퍼 클래스 자체를 사용하지 않아도 아래와 같이 할 수 있습니다. 제네릭이면 진짜 힘을 사용하도록 노력하십시오.
래퍼 클래스 자체를 사용하지 않아도 아래와 같이 할 수 있습니다. 제네릭이면 진짜 힘을 사용하도록 노력하십시오.
/** * * 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; }
from https://stackoverflow.com/questions/21987295/using-spring-resttemplate-in-generic-method-with-generic-parameter by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] Spring을 사용하여 JPA EntityManager를 주입하는 방법 (0) | 2018.12.30 |
---|---|
[SPRING] Spring3.2 및 jboss (7) (0) | 2018.12.30 |
[SPRING] 스프링 부트없는 스프링 부트 액츄에이터 (0) | 2018.12.30 |
[SPRING] Spring에서 프로그래밍 방식으로 현재 활성 / 기본 환경 프로파일을 얻는 방법은 무엇입니까? (0) | 2018.12.30 |
[SPRING] 봄에 AOP 로깅? (0) | 2018.12.30 |