#java #spring #collections #spring-mvc #jackson
#java #spring #Коллекции #spring-mvc #джексон
Вопрос:
Я пытаюсь упорядочить объекты list: List<Pojo>
с помощью шаблона Spring Rest.
Я могу передавать простые Pojo
объекты, но я не могу найти никакой документации, описывающей, как отправлять List<Pojo>
объекты.
Spring использует Jackson JSON для реализации HttpMessageConverter
. Документация jackson описывает это:
В дополнение к привязке к POJOs и «простым» типам, существует один дополнительный вариант: привязка к универсальным (типизированным) контейнерам. Этот случай требует специальной обработки из-за так называемого стирания типов (используется Java для реализации дженериков несколько обратно совместимым способом), что не позволяет вам использовать что-то вроде
Collection<String>.class
(которое не компилируется).Итак, если вы хотите связать данные в
Map<String,User>
, вам нужно будет использовать:
Map<String,User> result = mapper.readValue(src, new TypeReference<Map<String,User>>() {});
где
TypeReference
требуется только передать определение универсального типа (в данном случае через произвольный внутренний класс): важной частью является то,<Map<String,User>>
что определяет тип для привязки.
Можно ли это выполнить в шаблоне Spring? Я взглянул на код, и это заставляет меня сомневаться, но, возможно, я просто не знаю какого-то трюка.
Решение
Конечным решением, благодаря приведенным ниже полезным ответам, было не отправлять список, а скорее отправлять отдельный объект, который просто расширяет список, такой как: class PojoList extends ArrayList<Pojo>
. Spring может успешно маршалировать этот объект, и это выполняет то же самое, что и отправка List<Pojo>
, хотя это немного менее чистое решение. Я также опубликовал JIRA весной для них, чтобы устранить этот недостаток в их HttpMessageConverter
интерфейсе.
Ответ №1:
В Spring 3.2 теперь есть поддержка универсальных типов с использованием новых exchange()
методов на RestTemplate
:
ParameterizedTypeReference<List<MyBean>> typeRef = new ParameterizedTypeReference<List<MyBean>>() {};
ResponseEntity<List<MyBean>> response = template.exchange("http://example.com", HttpMethod.GET, null, typeRef);
Работает просто великолепно!
Комментарии:
1. Я не понимаю, как вы используете это для возврата объектов JSON. Разве это не накладывает на клиента функцию oness, чтобы убедиться, что он вызывает сервер с запросом определенного типа?
2. спасибо, это помогает.. после того, как у вас есть тело, вам нужно привести его к желаемому типу .. для примера выше это должно быть ((Список<MyBean>)response.getBody())
3. @AnoopIsaac Это может зависеть от того, какую версию Spring вы используете. В Spring 4.0 нет необходимости вводить тип тела; это уже тип,
List<MyBean>
в чем и заключается весь смысл параметризацииresponse
в первую очередь. Держу пари, что это, вероятно, верно и в Spring 3.2.
Ответ №2:
Один из способов убедиться, что включены параметры универсального типа, — это фактически создать подкласс List или Map type, чтобы у вас было что-то вроде:
static class MyStringList extends ArrayList<String> { }
и верните экземпляр этого списка.
Итак, почему это имеет значение? Поскольку информация об общем типе сохраняется всего в нескольких местах: в объявлениях методов и полей, а также в объявлениях супертипов. Итак, в то время как «необработанный» список не содержит никакой информации о типе среды выполнения, определение класса «MyStringList» содержит ее через объявления супертипов. Обратите внимание, что присвоения кажущимся типизированными переменным не помогают: это просто создает больше синтаксического сахара во время компиляции: информация о реальном типе передается только с экземплярами класса (или их расширениями, предоставляемыми библиотекой, такими как JavaType и TypeReference в случае Джексона).
Помимо этого, вам нужно было бы выяснить, как передать Jackson либо JavaType
, либо TypeReference
для сопровождения значения.
Ответ №3:
Если я правильно прочитал документы MappingJacksonHttpMessageConverter
, вам нужно будет создать и зарегистрировать подкласс MappingJacksonHttpMessageConverter
и переопределить getJavaType(Class<?>)
метод:
Возвращает JavaType Джексона для конкретного класса. Реализация по умолчанию возвращает TypeFactory.type(java.lang.reflect.Type), но это может быть переопределено в подклассах, чтобы обеспечить пользовательскую обработку общей коллекции. Например:
protected JavaType getJavaType(Class<?> clazz) {
if (List.class.isAssignableFrom(clazz)) {
return TypeFactory.collectionType(ArrayList.class, MyBean.class);
} else {
return super.getJavaType(clazz);
}
}
Комментарии:
1. Поправьте меня, если я неправильно понимаю проблему здесь, но передача класса в getJavaType требует удаления типа (вы не можете передать List<Pojo>.class), вы можете только передать List.class это означает, что у вас нет способа узнать, какой тип списка создавать (таким образом, вы в конечном итоге сможете создавать только общие объекты JSON, а не желаемые объекты Pojo в списке. Возможно, я здесь не прав, или мне нужно переформулировать проблему, чтобы понять это. Но, уделите минутку, чтобы обдумать это и дайте мне знать, что вы думаете. Насколько я понимаю, весь интерфейс HttpMessageConverter ограничивает вашу способность создавать типизированные коллекции.
2. @David нет, вы правильно понимаете, поэтому ваш подкласс должен был бы каким-то образом «знать», что
Mybean.class
такое. (Это может быть реализовано с помощью соглашений об именовании, пользовательских аннотаций и т.д.)
Ответ №4:
Я решил эту проблему, используя следующую конфигурацию:
private static final String POJO_ARRAY_LIST = PojoArrayList.class.getCanonicalName();
@Bean
public HttpMessageConverter<Object> httpMessageConverter() {
HttpMessageConverter<Object> httpMessageConverter = new MappingJackson2HttpMessageConverter() {
@Override
protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
JavaType javaType;
if (type != null amp;amp; POJO_ARRAY_LIST.equals(type.getTypeName())) {
ObjectMapper objectMapper = new ObjectMapper();
TypeFactory typeFactory = objectMapper.getTypeFactory();
CollectionType collectionType = typeFactory.constructCollectionType(ArrayList.class, Pojo.class);
javaType = collectionType;
} else {
javaType = super.getJavaType(type, contextClass);
}
return javaType;
}
};
return httpMessageConverter;
}
где PojoArrayList
это конечный класс, который расширяется ArrayList<Pojo>
.