Spring / json: преобразовать типизированную коллекцию в List

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