Служба преобразования Spring - из списка <A>в список <B> - PullRequest
14 голосов
/ 12 октября 2011

Я зарегистрировал пользовательскую службу преобразования в приложении Spring 3. Он хорошо работает для POJO, но не работает со списками.

Например, я конвертирую из String в Role, и он отлично работает, но не для List<String> в List<Role>.

Все виды ClassCastExceptions летают в приложении при попытке ввести Списки, независимо от того, что они содержат. Служба преобразования вызывает конвертер для List<String> до List<Role> для всех.

Это имеет смысл, если вы думаете об этом. Стирание типа является виновником здесь, и служба преобразования действительно видит List в List.

Есть ли способ заставить службу конвертации работать с генериками?

Какие еще есть варианты?

Ответы [ 8 ]

14 голосов
/ 07 ноября 2013

Я столкнулся с той же проблемой, и, проведя небольшое расследование, нашел решение (работает для меня).Если у вас есть два класса A и B, и у вас есть зарегистрированный преобразователь, например SomeConverter реализует Converter, то, чтобы преобразовать список A в список B, вы должны сделать следующее:

List<A> listOfA = ...
List<B> listOfB = (List<B>)conversionService.convert(listOfA,
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(A.class)),
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(B.class)));
13 голосов
/ 01 июня 2014

Другой способ конвертировать из List<A> в List<B> в Spring - использовать ConversionService#convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType). Javadoc

Для этого подхода просто необходим Converter<A,B>.

Вызов конверсионного сервиса для типов сбора:

List<A> source = Collections.emptyList();
TypeDescriptor sourceType = TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(A.class));
TypeDescriptor targetType = TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(B.class));
List<B> target = (List<B>) conversionService.convert(source, sourceType, targetType);

Преобразователь:

public class ExampleConverter implements Converter<A, B> {
    @Override
    public B convert(A source) {
        //convert
    }
}
5 голосов
/ 19 октября 2012

Я использую Spring GenericConversionService.

Рассматриваемый метод конвертации имеет следующую подпись:

public <T> T convert(Object source, Class<T> targetType)

List<B>.class - недопустимый синтаксис Java.

Это сработало для меня:

List<A> sourceList = ...;
conversionService.convert(sourceList, (Class<List<B>>)(Class<?>)List.class);

Получил идею отсюда: StackOverflow - Объект класса общего класса

Редактировать:

Выше не сработало.Нет ошибок компиляции, однако это привело к тому, что sourceList не был преобразован и был назначен для targetList.Это приводило к различным исключениям в нисходящем направлении при попытке использовать targetList.

Мое текущее решение состоит в том, чтобы расширить GenericConversionService в Spring и добавить собственный метод преобразования для обработки списков.

Вот метод преобразования:

@SuppressWarnings({"rawtypes", "unchecked"})
public <T> List<T> convert(List<?> sourceList, Class<T> targetClass) {

    Assert.notNull(sourceList, "Cannot convert null list.");
    List<Object> targetList = new ArrayList();

    for (int i = 0; i < sourceList.size(); i++) {
        Object o = super.convert(sourceList.get(i), targetClass);
        targetList.add(o);
    }

    return (List<T>) targetList;
}

И это можно назвать так:

List<A> sourceList = ...;
List<B> targetList = conversionService.convert(sourceList, B.class);

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

3 голосов
/ 11 ноября 2011

Ральф был прав, он работает, если у вас есть конвертер, который конвертирует из A в B.

Мне не нужен конвертер для List<A> в List<B>.

2 голосов
/ 06 сентября 2013

У меня была такая же проблема с dozer, и я нашел это: Как отобразить коллекции в dozer

Итак, чтобы сделать это с помощью службы преобразования пружин, я написал простой служебный метод:

public static <T, U> List<U> convertList(final ConversionService service, final List<T> source, final Class<U> destType) {

    final List<U> dest = new ArrayList<U>();

    if (source != null) {
        for (final T element : source) {
            dest.add(service.convert(element, destType));
        }
    }
    return dest;
}

По сути, единственное отличие состоит в том, что для этого требуется сервис преобразования пружин вместо экземпляра бульдозера.Теперь вы можете использовать этот метод для преобразования списков с типом безопасности.Это похоже на ответ Джеффа Б. выше, за исключением того, что вам не нужно подавлять предупреждения, и этот метод не обязательно должен принадлежать данному преобразованию ... это служебный метод.

2 голосов
/ 13 октября 2011

У меня есть обходной путь для вас.Это не самая красивая вещь в мире, но если для вас важно использовать сервис преобразования Spring, он может просто помочь.

Как вы указали, проблема заключается в удалении типа.Лучшее, что вы можете сказать Spring через интерфейс ConversionService, это то, что вы можете конвертировать List в List, что в Spring не имеет смысла, и именно поэтому конвертер не работает (это предположение с моей стороны. Я не думаю, что этоошибка, другими словами).

Чтобы сделать то, что вы хотите, вам нужно создать и использовать реализацию универсального интерфейса и / или класса для конкретного типа:

public interface StringList extends List<String> { }

public class StringArrayList extends ArrayList<String> implements StringList { }

InВ этом примере вы должны создать свой список с помощью StringArrayList вместо ArrayList и зарегистрировать либо класс реализации (StringArrayList.class), либо класс интерфейса (StringList.class) через интерфейс ConversionService.Кажется, вы хотите зарегистрировать интерфейс ... но если вы хотите зарегистрировать только класс реализации, вам вообще не нужно определять интерфейс.

Надеюсь, это поможет.

0 голосов
/ 08 мая 2017

И, если вы вне Java 7, всегда есть способ сделать это с потоками:

List<B> listB = listA.stream().map(a -> converterService.convert(a, B.class)).collect(Collectors.toList());
0 голосов
/ 01 марта 2014

Один относительно чистый обходной путь, который я обнаружил, заключался в создании класса прокси-конвертера, который принимает необработанный объект для преобразования и делегирует расширенную версию интерфейса Converter, которая поддерживает семантику boolean canConvert, чтобы реализация решить, может ли он преобразовать эти исходные данные или нет.

например:.

Интерфейс:

public interface SqlRowSetToCollectionConverter<T> extends Converter<SqlRowSet,Collection<T>> {
    boolean canConvert (SqlRowSet aSqlRowSet);
}

Класс прокси:

public class SqlRowSetToCollectionConverterService implements Converter<SqlRowSet, Collection<?>> {

    private SqlRowSetToCollectionConverter<?>[] converters;

    public void setConverters (SqlRowSetToCollectionConverter<?>[] aConverters) {
        converters = aConverters;
    }

    @Override
    public Collection<?> convert (SqlRowSet aSource) {
        for (SqlRowSetToCollectionConverter<?> converter : converters) {
            if (converter.canConvert (aSource)) {
                return (converter.convert(aSource));
            }
        }
        return null;
    }

}

Затем я бы зарегистрировал прокси-класс в службе преобразования Spring и зарегистрировал в прокси-классе все реализации расширенного интерфейса:

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
          <bean class="com.hilton.hms.data.convert.SqlRowSetToCollectionConverterService">
             <property name="converters">
               <bean class="com.hilton.hms.data.convert.SqlRowSetToConfigCollectionConverter" />
             </property>
          </bean>
        </set>
    </property>
</bean>
...