9 に答える
私は同じ問題に遭遇し、少し調査を行うことで解決策を見つけました(私にとってはうまくいきます)。A と B の 2 つのクラスがあり、登録済みのコンバーターがある場合 (たとえば、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)));
Spring GenericConversionService を使用しています。
問題の convert メソッドには、次のシグネチャがあります。
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 - ジェネリック クラスのクラス オブジェクト
編集:
上記は実際には機能しませんでした。コンパイル エラーは発生しませんでしたが、sourceList が変換されず、targetList に割り当てられました。これにより、targetList を使用しようとしたときに、ダウンストリームでさまざまな例外が発生しました。
私の現在の解決策は、Spring の GenericConversionService を拡張し、独自の convert メソッドを追加してリストを処理することです。
変換方法は次のとおりです。
@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);
この一般的なシナリオを処理するためのより良い方法があるかどうかを知りたいです。
A
Ralph は正しかったです。Toから変換するコンバーターがあれば動作しますB
。
toのコンバーターは必要ありませんでしList<A>
たList<B>
。
回避策があります。それは世界で最も美しいものではありませんが、Spring 変換サービスを使用することが重要な場合は、うまくいくかもしれません。
あなたが指摘したように、問題は型消去です。ConversionService インターフェースを介して Spring に伝えることができる最善の方法は、List を List に変換できることです。これは Spring にとって意味がなく、それがコンバーターが機能しない理由です (これは私の推測です..バグ、つまり)。
必要なことを行うには、ジェネリック インターフェイスやクラスの型固有の実装を作成して使用する必要があります。
public interface StringList extends List<String> { }
public class StringArrayList extends ArrayList<String> implements StringList { }
この例では、ArrayList の代わりに StringArrayList を使用してリストを作成し、ConversionService インターフェイスを介して実装クラス (StringArrayList.class) またはインターフェイス クラス (StringList.class) を登録します。インターフェースを登録したいようですが、実装クラスのみを登録したい場合は、インターフェースを定義する必要はまったくありません。
これが役立つことを願っています。
私が見つけた比較的クリーンな回避策の 1 つは、変換される生のオブジェクトを受け入れるプロキシ コンバーター クラスを作成し、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>
GenericConversionService にメソッドが追加されました
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor targetType) {
return convert(source, TypeDescriptor.forObject(source), targetType);
}
そのため、TypeDescriptor を 1 つだけ渡すことができます
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))));
}