我使用ModelMapper将模型转换为DTO。我有一堆默认的转换器,用于像这样在映射器级别注册的空值:
modelMapper.addConverter(new Converter<String, String>() {
@Override
public String convert(MappingContext<String, String> context) {
if (context.getSource() == null) {
return "global null converter was here";
}
return context.getSource();
}
});
当属性的名称在转换的两侧相同时,这对于简单的映射很有效。 该转换器用于按预期处理空值。
现在,如果我需要通过在类型映射上使用.map(getter,setter)进行具有不同属性名称的更复杂的转换,则不再调用全局转换器。 我不希望在配置类型映射时丢弃全局转换器。
我该如何解决?
以下是使用ModelMapper 2.3.8(今天的最新版本)的示例代码(带有lombok的代码简洁性):
@Data @AllArgsConstructor @NoArgsConstructor class A { String a; String b;}
@Data @AllArgsConstructor @NoArgsConstructor class B { String a; String b; }
@Data @AllArgsConstructor @NoArgsConstructor class C { String x; String y;}
public class MapperTestCase {
public static void main(String[] args) throws IOException {
A a = new A("aaa", "bbb");
ModelMapper modelMapper = new ModelMapper();
final TypeMap<A, B> AtoBTypeMap = modelMapper.createTypeMap(A.class, B.class);
B b = AtoBTypeMap.map(a);
System.out.println("conversion with no converter A -> B: " + a + " -> " + b);
a = new A(null, null);
b = AtoBTypeMap.map(a);
System.out.println("conversion with no converter A -> B: " + a + " -> " + b);
// Add a global/fallback converter that should convert all null String values.
modelMapper.addConverter(new Converter<String, String>() {
@Override
public String convert(MappingContext<String, String> context) {
if (context.getSource() == null) {
return "global null converter was here";
}
return context.getSource();
}
});
final TypeMap<B, A> BtoATypeMap = modelMapper.typeMap(B.class, A.class);
a = BtoATypeMap.map(b);
System.out.println("conversion with global converter B -> A: " + b + " -> " + a);
// add a local converter for the B to C type mape only
BtoATypeMap.addMappings(mapper -> mapper.using(ctx -> {
if (ctx.getSource() == null) {
return "local converter was here";
} else return ctx.getSource();
}).map(B::getA, (w, x) -> w.setA(String.valueOf(x))));
// in this conversion both converter (global and local) should be used
a = BtoATypeMap.map(b);
System.out.println("conversion with global and local converter B -> A: " + b + " -> " + a);
// a new typeMap that will transform a B into a C, mapping B::a to C::x and B::b to C::y
final TypeMap<B, C> BtoCTypeMap = modelMapper.typeMap(B.class, C.class);
// a local converter for this type map
BtoCTypeMap.addMappings(mapper -> mapper.using(ctx -> {
if (ctx.getSource() == null) {
return "local converter was here";
} else return ctx.getSource();
}).map(B::getA, (w, x) -> w.setX(String.valueOf(x))));
BtoCTypeMap.addMapping(B::getB, C::setY);
// first a conversion with a B instance without null values, works as expected
b = new B("some", "data");
C c = BtoCTypeMap.map(b);
System.out.println("conversion with global and local converter B -> C: " + b + " -> " + c);
// now a conversion with a B instance wirth null values, the local converer will be used, but not the global one defined at the mapper level. Why ?
b = new B();
c = BtoCTypeMap.map(b);
System.out.println("conversion with global and local converter B -> C: " + b + " -> " + c);
}
}
输出为:
conversion with no converter A -> B: A(a=aaa, b=bbb) -> B(a=aaa, b=bbb)
conversion with no converter A -> B: A(a=null, b=null) -> B(a=null, b=null)
conversion with global converter B -> A: B(a=null, b=null) -> A(a=global null converter was here, b=global null converter was here)
conversion with global and local converter B -> A: B(a=null, b=null) -> A(a=local converter was here, b=global null converter was here)
conversion with global and local converter B -> C: B(a=some, b=data) -> C(x=some, y=data)
conversion with global and local converter B -> C: B(a=null, b=null) -> C(x=local converter was here, y=null)
最后一行的预期输出是C(x =本地转换器在这里,y =全局空转换器在这里)
答案 0 :(得分:2)
如果您想创建常规的propertyConverter,可以尝试这样的操作
Converter<String, String> stringPropertyConverter = new Converter<String, String>() {
@Override
public String convert(MappingContext<String, String> context) {
if (context.getSource() == null) {
return "global null converter was here";
}
return context.getSource();
}
};
ModelMapper modelMapper = new ModelMapper() {
@Override
public <S, D> TypeMap<S, D> typeMap(Class<S> sourceType, Class<D> destinationType) {
TypeMap<S, D> typeMap = super.typeMap(sourceType, destinationType);
typeMap.setPropertyConverter(stringPropertyConverter);
return typeMap;
}
};
在映射过程中使用的顺序转换器通常存在问题。 首先,modelMapper为您的类定义转换器,在下一步中,它为类的字段搜索合适的转换器。 在第一种情况下,您的转换器按顺序排列
"TypeMap[String -> String]"
"TypeMap[B -> A]"
"TypeMap[A -> B]"
在第二种情况下
"TypeMap[B -> C]"
"TypeMap[String -> String]"
"TypeMap[B -> A]"
"TypeMap[A -> B]"
并且转换器B到C是适合您班上任何字段的转换器。
答案 1 :(得分:1)
我想我找到了解决方法:
这是因为类C的属性名称与类A和B的属性名称不同。如果将x重命名为a,将y重命名为b,则输出将很好。
您现在的问题是“为什么这样工作”,仅是因为modelMapper仅在两个对象之间的名称相同时才应用转换器。我认为modelMapper不会通过忽略属性名称为真正的“全局”转换器提供解决方案。
关于您的代码,我认为您应该使用Java 8功能:
modelMapper.addConverter(new Converter<String, String>() {
@Override
public String convert(MappingContext<String, String> context) {
if (context.getSource() == null) {
return "global null converter was here";
}
return context.getSource();
}
});
可以重写:
modelMapper.addConverter(context -> context.getSource() == null ? "global null converter was here" : context.getSource());
// or much better because you extract your mapping logic :
modelMapper.addConverter(mySuperConverter());
private static Converter<String, String> mySuperConverter() {
return context -> context.getSource() == null ? "global null converter was here" : context.getSource();
}
答案 2 :(得分:1)
我必须认识到我通常使用MapStruct或Dozer,但是我时常使用ModelMapper。
话虽如此,我将尝试解释使用该库时遵循的思维模式:希望它能帮助您理解问题。
当您在ModelMapper中定义源类和目标类之间的映射时,实际上是在定义它们之间的对应关系mapping
。
如果您没有在源类的一个属性和目标类的另一个属性之间定义显式属性,则会发生隐式映射。
此隐式映射基于多个matching policies,但是我们可以放心地说,它基于属性名称匹配。
如果您在Converter
级别定义了ModelMapper
,则只有在未提供一个显式属性mapping
时,它才会应用于属性mapping
,以下情况原因:如果您使用方法mapping
或TypeMap
在addMapping
中的任何属性之间定义了显式属性addMappings
,则为该显式{{1}提供的配置}(源获取器和目标设置器,转换器,前置转换器,后置转换器)将是mapping
流程中唯一的组件,无论您在更高的映射级别上定义什么。
您可以通过调试程序来轻松测试这一事实,并逐行查看库如何定义基础属性mapping
。
由于这个原因,我认为无法实现这种全局行为:您可以做的是通过可能实现其他答案或更佳建议的工厂方法(通过创建特定的mapping
类来重复该行为)您可以实例化并设置需要的每个Converter
和属性TypeMap
的转换器(在您的用例中,也许是后转换器)。
stackoverflow中有an excelent post,将为您提供关于使用ModelMapper时内部发生的情况的更好而更好的解释。