Использование 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;}
}

поэтому, если я пытаюсь сделать что-то подобное, все в порядке.

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>> объект, который я получаю

7 ответов


нет, это не ошибка. Это результат того, как ParameterizedTypeReference hack работает.

если вы посмотрите на его осуществление, он использует Class#getGenericSuperclass() в котором говорится

возвращает тип, представляющий прямой суперкласс сущности (класс, интерфейс, примитивный тип или void), представленный этим классом.

если суперкласс является параметризованным типом, the Type "объект" возвращается должен точно отражать фактический тип параметры, используемые в источнике код.

если вы используете

new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}

он точно вернет Type на ResponseWrapper<MyClass>.

если вы используете

new ParameterizedTypeReference<ResponseWrapper<T>>() {}

он точно вернет Type на ResponseWrapper<T> потому что так это выглядит в исходном коде.

Когда весна увидит T, что на самом деле является TypeVariable object, он не знает тип для использования, поэтому он использует его по умолчанию.

вы нельзя использовать ParameterizedTypeReference то, как вы предлагаете, делая его общим в смысле принятия любого типа. Подумайте о написании Map ключ Class привязан стандартный ParameterizedTypeReference для этого класса.

вы можете подкласс ParameterizedTypeReference и переопределить его getType метод для возврата соответствующим образом созданного ParameterizedType, как предложено IonSpin.


как объясняет Сотириос, вы не можете использовать 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>>());

со всей информацией о типе настоящего объекта mapper правильно построит ваш ResponseWrapper<MyClass> объект


Как показывает приведенный ниже код, он работает.

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

}

на самом деле, вы можете сделать это, но с дополнительным кодом.

здесь гуавы эквивалентно 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 APIs require ParameterizedTypeReference, а не TypeToken. Но мы можем создать 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<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>>() {});

у меня есть другой способ сделать это... предположим, вы меняете свой конвертер сообщений на String для своей RestTemplate, тогда вы можете получить raw JSON. Используя raw JSON, вы можете сопоставить его в свою общую коллекцию с помощью сопоставления объектов Jackson. Вот как:

Swap out конвертер сообщений:

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

Примечание: этот ответ ссылается/добавляет к ответу и комментарию Sotirios Delimanolis.

Я попытался заставить его работать с Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>>, как указано в комментарии Сотириоса, но не может без примера.

в конце концов, я отбросил подстановочный знак и параметризацию из 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);

и это, наконец, работал.

Если у кого-то есть пример с параметризацией, я был бы очень рад это видеть.


вы можете сделать это, как показано ниже, без использования самого класса оболочки. Попробуйте использовать реальную силу, если generics

/**
 * 
 * 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;
}