복붙노트

[SPRING] 제네릭 매개 변수를 사용하는 제네릭 메서드에서 Spring RestTemplate 사용

SPRING

제네릭 매개 변수를 사용하는 제네릭 메서드에서 Spring RestTemplate 사용

Spring RestTemplate으로 제네릭 타입을 사용하려면 ParameterizedTypeReference (일반 ResponseEntity 를 얻을 수 없습니다. 여기서 T는 제네릭 클래스 "SomeClass "입니다)를 사용해야합니다.

수업이 있다고 가정 해 봅시다.

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 > 개체를 가져오고 있습니다. ResponseEntity > 개체가 생성됩니다.

이 문제를 어떻게 해결할 수 있습니까? RestTemplate 버그입니까?

업데이트 1 @Sotirios 덕분에 나는 개념을 이해한다. 불행하게도 나는 새로 등록 된 사람이다. 그래서 나는 그의 대답에 대해 코멘트를 할 수 없다. 나는 클래스 키를 가진 맵으로 내 문제를 해결하기 위해 제안 된 접근법을 구현하는 방법을 분명히 이해하지 못했다. (그의 대답 끝에 @Sotirios가 제안 함). 누군가 모범을 보이겠습니까?

해결법

  1. ==============================

    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. ==============================

    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. ==============================

    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. ==============================

    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. ==============================

    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. ==============================

    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. ==============================

    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;
    }
    
  8. from https://stackoverflow.com/questions/21987295/using-spring-resttemplate-in-generic-method-with-generic-parameter by cc-by-sa and MIT license