Служба преобразования Spring - из списка <a>в список</a>

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

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

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

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

Есть ли способ сообщить службе конверсии работать с дженериками?

Какие у меня есть другие варианты?

Ответ 1

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

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

Ответ 2

Я столкнулся с той же проблемой, и, проведя небольшое исследование, нахожу решение (работает для меня). Если у вас есть два класса A и B и у вас есть зарегистрированный конвертер, например SomeConverter реализует Конвертер, чем, чтобы преобразовать список 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)));

Ответ 3

Другой способ конвертировать из List<A> в List<B> весной - использовать 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
    }
}

Ответ 4

Я использую 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);

Получил идею отсюда: fooobar.com/questions/118078/...

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

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

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

Здесь метод преобразования:

@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);

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

Ответ 5

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

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

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

public interface StringList extends List<String> { }

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

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

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

Ответ 6

У меня была такая же проблема с бульдозером, и я нашел следующее: Как сопоставить коллекции в бульдозерах

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

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

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

Ответ 7

Одной из относительно чистых задач, которые я нашел, было создание класса конвертера прокси, который принимает необработанный объект, который будет преобразован, и делегирует его в расширенную версию интерфейса конвертера, которая поддерживает boolean canConvert семантику 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 Conversion и зарегистрирую в прокси-классе все расширенные реализации интерфейса:

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

Ответ 8

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

List<B> listB = listA.stream().map(a -> converterService.convert(a, B.class)).collect(Collectors.toList());