Spring转换服务 - 来自List <a> to List<b></b></a>

时间:2011-10-12 10:00:00

标签: java spring type-conversion spring-3

我在Spring 3应用程序中注册了自定义转换服务。它适用于POJO,但它不适用于列表。

例如,我从String转换为Role,但效果很好,但不适用于List<String>List<Role>

当尝试注入列表时,所有类型的ClassCastExceptions都会在应用程序中飞行,无论它们包含什么。转化服务会将List<String>的转换器调用为List<Role>

如果您考虑一下,这是有道理的。类型擦除是罪魁祸首,转换服务实际上会将List视为List

有没有办法告诉转换服务使用泛型?

我还有其他选择吗?

9 个答案:

答案 0 :(得分:14)

我遇到了同样的问题,通过进行一点调查找到解决方案(适合我)。如果你有两个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)));

答案 1 :(得分:13)

在Spring中从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
    }
}

答案 2 :(得分:5)

我正在使用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 - Class object of generic class

修改

以上并没有真正起作用。没有编译错误,但是它导致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);

很高兴看到是否有人有更好的方法来处理这种常见情况。

答案 3 :(得分:3)

Ralph是正确的,如果你有一个转换器从A转换为B,它就有效。

我不需要List<A>List<B>的转换器。

答案 4 :(得分:2)

我为你解决了问题。它不是世界上最漂亮的东西,但是如果使用Spring转换服务对你来说很重要,它可能就是这样做的。

正如您所指出的那样,问题是类型擦除。你可以通过ConversionService接口告诉Spring最好的是你可以将List转换为List,这对Spring来说没有意义,这就是转换器无法工作的原因(这是我的猜测..我不认为它是一个换句话说,bug。。

要执行您想要的操作,您需要创建和使用通用接口和/或类的特定于类型的实现:

public interface StringList extends List<String> { }

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

在此示例中,您将使用StringArrayList而不是ArrayList创建列表,并通过ConversionService接口注册实现类(StringArrayList.class)或接口类(StringList.class)。看起来你想要注册接口..但如果你只想注册实现类,那么你根本不需要定义接口。

我希望这会有所帮助。

答案 5 :(得分:2)

我在推土机上遇到了同样的问题,发现了这个问题: How to map collections in dozer

为了用Spring转换服务做到这一点,我写了一个简单的实用方法:

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

本质上唯一的区别是它采用了spring转换服务而不是dozer映射器实例。您现在可以使用此方法转换类型安全的List。这类似于Jeff B的上述答案,除了你不需要压制警告,这个方法不一定需要属于给定的转换......它是一种实用方法。

答案 6 :(得分:0)

我发现一个相对干净的解决方法是创建一个代理转换器类,它接受要转换的原始对象,并委托给支持boolean canConvert语义的Converter接口的扩展版本,以便允许实现以决定它是否可以转换该源数据。

e.g:

界面:

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>

答案 7 :(得分:0)

而且,如果你超越Java 7,总有办法用流来做到这一点:

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

答案 8 :(得分:0)

GenericConversionService现在具有方法

@Nullable
public Object convert(@Nullable Object source, TypeDescriptor targetType) {
  return convert(source, TypeDescriptor.forObject(source), targetType);
}

所以现在,我们只能传递一个TypeDescriptor

public <S, @NonNull T> Collection<T> convert(Collection<S> source, Class<T> targetType) {
  return (Collection<@NonNull T>) requireNonNull(conversionService.convert(source, TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(targetType))));
}