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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 21:13:06  来源:igfitidea点击:

Spring conversion service - from List<A> to List<B>

javaspringtype-conversionspring-3

提问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 Stringto Roleand it works fine, but not for List<String>to List<Role>.

例如,我从Stringto转换Role并且它工作正常,但不适用于List<String>to List<Role>

All kind of ClassCastExceptionsfly 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 Listto List.

如果你仔细想想,这是有道理的。类型擦除是这里的罪魁祸首,转换服务实际上看到ListList.

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 ATo B.

拉尔夫是对的,如果您有一个从ATo转换的转换器,它就可以工作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>.classis 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 canConvertsemantics 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>