【问题标题】:Spring conversion service - from List<A> to List<B>Spring 转换服务 - 从 List<A> 到 List<B>
【发布时间】:2011-12-06 00:02:43
【问题描述】:

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

例如,我从String 转换为Role 并且它工作正常,但不适用于List&lt;String&gt;List&lt;Role&gt;

尝试注入列表时,所有类型的ClassCastExceptions 都会在应用程序中飞来飞去,无论它们包含什么。转换服务为所有人调用 List&lt;String&gt;List&lt;Role&gt; 的转换器。

如果您考虑一下,这是有道理的。类型擦除是这里的罪魁祸首,转换服务实际上看到 ListList

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

我还有什么其他选择?

【问题讨论】:

  • 如果您在运行时遇到 ClassCastExceptions,则问题不在于泛型,而在于您的代码。泛型仅用于编译器。泛型应该在这里可用。你能展示一下这个转换方法的代码吗?
  • @andypandy:在 Spring 中,我可以使用 Converter 接口注册转换器。由于这是通用的,因此最简洁的代码使用以下实现 Converter&lt;List&lt;String&gt;, List&lt;Role&gt;&gt;。当然,由于类型擦除,这不起作用。但问题不在于代码,而是 Spring 在运行时将转换器应用于所有列表,即使是那些不包含字符串或角色的列表。
  • 现在我明白了 - 认为这是您自己的服务/服务处理。听起来您只能注册一个列表转换器,该转换器又应该包装并转发到您选择的列表转换器。虽然不是 Spring Converter 功能方面的专家。
  • @andypandy:是的,这是我想到的一种解决方法,但我真的不喜欢 instanceof 测试或在元素上匹配类名来确定类型。我在网上搜索了高低,但找不到列表的东西。在采取一些解决方法之前,我认为 SO 社区可以帮助我解决我可能错过的一些问题。
  • 我上次对 spring 转换实现进行了非常深入的研究。如果您有一个从字符串转换为角色的转换器,它应该可以工作(对于 List 到 List,您不需要额外的转换器)但我也发现了一个非常奇怪的行为(可能是一个错误) 如果您从List 转换为List,转换器将不起作用。但是,如果您从 List 转换为 Set,它会起作用! -- 如果您能确认相同的行为,我将再次查看代码,并可能会提出罚单。

标签: java spring type-conversion spring-3


【解决方案1】:

在 Spring 中从 List&lt;A&gt; 转换为 List&lt;B&gt; 的另一种方法是使用 ConversionService#convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)Javadoc

这种方法只需要Converter&lt;A,B&gt;

为集合类型调用转换服务:

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

【讨论】:

  • 我对这个解决方案投了赞成票,因为它阐明了该方法需要Converter&lt;A,B&gt;
【解决方案2】:

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

【讨论】:

  • 不知道为什么,但底部标记为正确的答案对我有用,但只有一个实例。我让转换器简单地将 A.class 转换为 B.class,然后我可以传入 A.class 列表并告诉它返回类型 List.class,它工作正常。在一种情况下,它只会返回我传入的原始列表。使用此解决方案对我有用。
【解决方案3】:

我正在使用 Spring GenericConversionService。

相关的转换方法具有以下签名:

public <T> T convert(Object source, Class<T> targetType)

List&lt;B&gt;.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);

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

【讨论】:

  • 您能否添加一个有关如何配置此类自定义 ConversionService 的工作示例?
【解决方案4】:

Ralph 是正确的,如果您有一个从 A 转换为 B 的转换器,它就可以工作。

我不需要将List&lt;A&gt; 转换为List&lt;B&gt; 的转换器。

【讨论】:

  • 我无法让它工作。你做了什么让它运行?您使用的是哪个 Spring 版本?我试过 T convert(Object source, Class targetType) 但没有成功。
  • 转换器实现将只是“实现 。实现它以将 ClassA 的一个实例转换为 ClassB。然后调用“List convertList = conversionService.convert(listOfClassA, List. class);" 除了我的一次转换之外,这对我来说都有效。在一个实例中它不起作用,转换服务只是简单地返回 ClassA 的原始列表。为此,上面 nndru 提供的答案解决了问题。
【解决方案5】:

我有一个解决方法给你。这不是世界上最漂亮的东西,但如果使用 Spring 转换服务对您很重要,它可能会成功。

正如您所指出的,问题是类型擦除。您可以通过 ConversionService 接口告诉 Spring 的最好方法是您可以将 List 转换为 List,这对 Spring 没有任何意义,这就是转换器不起作用的原因(这是我的猜测 .. 我不认为这是换句话说,错误)。

要做你想做的事,你需要创建和使用通用接口和/或类的特定类型实现:

public interface StringList extends List<String> { }

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

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

我希望这会有所帮助。

【讨论】:

  • +1 表示这个想法,但这并不意味着我的 bean 必须包含 StringList 类型的属性(在我的特定情况下为 ProfileList)而不是 List&lt;Profile&gt; 以便转换服务识别适当的目标类型并应用适当的转换器?
  • @JohnDoDo 是的,这是真的。我保证它不漂亮,所以非常感谢 +1。
  • @RichW 另一种方法是创建两个包装器(类) WrapperA、WrapperB 并将列表放入其中。之后,您可以创建通常的转换器,通过标准转换方法传递包装器。
【解决方案6】:

我在推土机上遇到了同样的问题,发现了这个: 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;
}

本质上,唯一的区别是它采用弹簧转换服务而不是推土机映射器实例。您现在可以使用此方法来转换具有类型安全性的 List。这类似于上面 Jeff B 的回答,只是您不需要禁止显示警告,并且此方法不一定需要属于给定的 Convert...它是一种实用方法。

【讨论】:

    【解决方案7】:

    而且,如果您已经超越 Java 7,总有一种方法可以使用流:

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

    【讨论】:

      【解决方案8】:

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

      例如:

      界面:

      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>
      

      【讨论】:

        【解决方案9】:

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

        【讨论】:

          猜你喜欢
          • 2019-10-23
          • 1970-01-01
          • 2021-04-03
          • 2011-12-03
          • 1970-01-01
          • 1970-01-01
          • 2011-03-03
          • 2019-12-09
          • 1970-01-01
          相关资源
          最近更新 更多