java Spring 转换服务——从 List<A> 到 List<B>
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7738305/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring conversion service - from List<A> to List<B>
提问by JohnDoDo
I have registered a custom conversion service in a Spring 3 application. It works well for POJOs but it does not work on Lists.
我在 Spring 3 应用程序中注册了自定义转换服务。它适用于 POJO,但不适用于列表。
For example, I convert from String
to Role
and it works fine, but not for List<String>
to List<Role>
.
例如,我从String
to转换Role
并且它工作正常,但不适用于List<String>
to List<Role>
。
All kind of ClassCastExceptions
fly in the application when trying to inject Lists, no matter what they contain. The Conversion service calls the convertor for List<String>
to List<Role>
for all.
所有那种ClassCastExceptions
试图注入列表,不管它们包含什么,当应用程序中的苍蝇。转换服务调用转换为List<String>
对List<Role>
所有。
This makes sense if you think about it. Type erasure is the culprit here and the convertion service actually sees List
to List
.
如果你仔细想想,这是有道理的。类型擦除是这里的罪魁祸首,转换服务实际上看到List
了List
.
Is there a way to tell the conversion service to work with generics?
有没有办法告诉转换服务使用泛型?
What other options do I have?
我还有什么其他选择?
采纳答案by JohnDoDo
Ralph was correct, it works if you have a converter that converts from A
To B
.
拉尔夫是对的,如果您有一个从A
To转换的转换器,它就可以工作B
。
I didn't need a converter for List<A>
to List<B>
.
我不需要转换器List<A>
to List<B>
。
回答by nndru
I've encountered same problem, and by performing a little investigation find a solution (works for me). If you have two classes A and B, and have a registered converter e.g. SomeConverter implements Converter, than, to convert list of A to list of B you should do next:
我遇到了同样的问题,并通过进行一些调查找到了解决方案(对我有用)。如果你有两个类 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)));
回答by Kamill Sokol
Another way to convert from List<A>
to List<B>
in Spring is to use ConversionService#convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)
. Javadoc
另一种方法是从转换List<A>
到List<B>
春季是使用ConversionService#convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)
。Javadoc
This approach just needs a Converter<A,B>
.
这种方法只需要一个Converter<A,B>
.
Call to conversionService for collection types:
调用conversionService 获取集合类型:
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);
The converter:
转换器:
public class ExampleConverter implements Converter<A, B> {
@Override
public B convert(A source) {
//convert
}
}
回答by Jeff B
I'm using the Spring GenericConversionService.
我正在使用 Spring GenericConversionService。
The convert method in question has the following signature:
有问题的 convert 方法具有以下签名:
public <T> T convert(Object source, Class<T> targetType)
List<B>.class
is not valid Java syntax.
List<B>.class
不是有效的 Java 语法。
This worked for me:
这对我有用:
List<A> sourceList = ...;
conversionService.convert(sourceList, (Class<List<B>>)(Class<?>)List.class);
Got the idea from here: StackOverflow - Class object of generic class
从这里得到的想法: StackOverflow - 泛型类的类对象
Edit:
编辑:
The above did not truly work. No compile errors, however it resulted in the sourceList not being converted, and being assigned to the targetList. This resulted in various exceptions downstream while attempting to use the targetList.
以上并没有真正起作用。没有编译错误,但是它导致 sourceList 没有被转换,并被分配给 targetList。这导致在尝试使用 targetList 时下游出现各种异常。
My current solution is to extend Spring's GenericConversionService and add my own convert method to handle lists.
我目前的解决方案是扩展 Spring 的 GenericConversionService 并添加我自己的 convert 方法来处理列表。
Here's the convert method:
这是转换方法:
@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;
}
And it can be called like the following:
它可以像下面这样调用:
List<A> sourceList = ...;
List<B> targetList = conversionService.convert(sourceList, B.class);
Love to see if anyone has a better way to handle this common scenario.
很想看看是否有人有更好的方法来处理这种常见情况。
回答by Mike
I had this same problem with dozer and found this: How to map collections in dozer
我在推土机上遇到了同样的问题,并发现了这个问题: 如何在推土机中映射集合
So to do this with spring conversion service I wrote a simple utility method:
所以为了用 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;
}
Essentially the only difference is that this takes in a spring conversion service instead of a dozer mapper instance. You can now use this method to convert List's with type safety. This is similar to Jeff B's answer above except that you don't need to suppress warnings and this method doesn't necessarily need to belong to a given Convert...it's a utility method.
本质上唯一的区别是这需要一个 spring 转换服务而不是一个推土机映射器实例。您现在可以使用此方法来转换具有类型安全性的 List。这类似于上面 Jeff B 的回答,不同之处在于您不需要抑制警告,并且此方法不一定需要属于给定的 Convert...它是一种实用方法。
回答by RichW
I have a work-around for you. It's not the prettiest thing in the world, but if using the Spring conversion service is important to you it might just do the trick.
我有一个解决方法给你。这不是世界上最漂亮的东西,但如果使用 Spring 转换服务对您很重要,那么它可能会奏效。
As you pointed out the problem is type erasure. The best you can tell Spring via the ConversionService interface is that you can convert List to List, which to Spring makes no sense and that's why the converter doesn't work (that's a guess on my part .. I don't think it's a bug, in other words).
正如您所指出的,问题是类型擦除。您可以通过 ConversionService 接口告诉 Spring 的最好情况是您可以将 List 转换为 List,这对 Spring 没有意义,这就是转换器不起作用的原因(这是我的猜测。我不认为这是一个错误,换句话说)。
To do what you want you'll need to create and use a type-specific implementation of the generic interface and/or class:
要执行您想要的操作,您需要创建和使用通用接口和/或类的特定于类型的实现:
public interface StringList extends List<String> { }
public class StringArrayList extends ArrayList<String> implements StringList { }
In this example you would create your list using StringArrayList instead of ArrayList and register either the implementation class (StringArrayList.class) or the interface class (StringList.class) via the ConversionService interface. It seems like you want to register the interface .. but if you only want to register the implementation class then you don't need to define the interface at all.
在此示例中,您将使用 StringArrayList 而不是 ArrayList 创建列表,并通过 ConversionService 接口注册实现类 (StringArrayList.class) 或接口类 (StringList.class)。看起来你想注册接口..但是如果你只想注册实现类,那么你根本不需要定义接口。
I hope this helps.
我希望这有帮助。
回答by skwisgaar
And, if you're beyond Java 7, there's always a way to do it with streams:
而且,如果你已经超越了 Java 7,总有一种方法可以用流来做到这一点:
List<B> listB = listA.stream().map(a -> converterService.convert(a, B.class)).collect(Collectors.toList());
回答by OliverLJ
GenericConversionService has now the method
GenericConversionService 现在有方法
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor targetType) {
return convert(source, TypeDescriptor.forObject(source), targetType);
}
so now, we can pass only one TypeDescriptor
所以现在,我们只能传递一个 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))));
}
回答by acohen
One relatively clean work-around I found was to create a proxy converter class which accepts the raw object to be converted and delegates to an extended version of the Converter interface which supports a boolean canConvert
semantics so as to allow the implementation to decide whether or not it can convert that source data or not.
我发现的一个相对干净的解决方法是创建一个代理转换器类,它接受要转换的原始对象并委托给 Converter 接口的扩展版本,该接口支持boolean canConvert
语义,以便允许实现决定它是否可以是否转换该源数据。
e.g.:
例如:
The interface:
界面:
public interface SqlRowSetToCollectionConverter<T> extends Converter<SqlRowSet,Collection<T>> {
boolean canConvert (SqlRowSet aSqlRowSet);
}
The proxy class:
代理类:
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;
}
}
Then I would register the proxy class with Spring's Conversion Service and register with the proxy class all the extended interface implementations:
然后我将使用 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>