我在Spring 3应用程序中注册了自定义转换服务。它适用于POJO,但它不适用于列表。
例如,我从String
转换为Role
,但效果很好,但不适用于List<String>
到List<Role>
。
当尝试注入列表时,所有类型的ClassCastExceptions
都会在应用程序中飞行,无论它们包含什么。转化服务会将List<String>
的转换器调用为List<Role>
。
如果您考虑一下,这是有道理的。类型擦除是罪魁祸首,转换服务实际上会将List
视为List
。
有没有办法告诉转换服务使用泛型?
我还有其他选择吗?
答案 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))));
}