我使用MapStruct从来自2个源bean的属性映射目标bean。这是MapStruct通过控制嵌套映射轻松完成的非常常见的事情。我的问题是我需要“计算/验证”一个目标属性,将源bean中的一个属性作为输入。
我将用一些代码片段来解释。
首先,我们有豆子,类似(简化):
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Getter
@Builder
public class TargetBean {
private final List<String> targetListTransformed;
private final List<String> targetListDirectMapping;
private final List<String> targetListTransformedWithAnotherMethod;
private final String targetString;
private final Integer targetInteger;
}
源豆之一:
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.List;
@Getter
@RequiredArgsConstructor
public class SourceBeanOne {
private final List<String> sourceListToTransform;
private final List<String> sourceListDirectMapping;
private final String sourceString;
}
和另一个源bean:
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class SourceBeanTwo {
private final Integer sourceInteger;
}
然后映射尝试:
@Mapper(uses = SourceOneExtractor.class)
public abstract class TargetBeanMapper {
@Mapping(target = "targetListTransformed", source = "source1")
@Mapping(target = "targetListDirectMapping", source = "source1.sourceListDirectMapping")
@Mapping(target = "targetListTransformedWithAnotherMethod", qualifiedByName = {"sourceOneTransformer", "transformerTwo"})
@Mapping(target = "targetString", source = "source1.sourceString")
@Mapping(target = "targetInteger", source = "source2.sourceInteger")
abstract TargetBean map (SourceBeanOne source1, SourceBeanTwo source2);
List<String> mapTargetList(SourceBeanOne source1) {
final List<String> result = new ArrayList<>();
// simulate a transformation
result.add("one");
result.add("two");
return result;
}
}
如您所见,我尝试了Invoking自定义映射方法,您可以在其中看到我需要做的一个虚拟实现:使用SourceBeanOne中的一个或多个属性来生成要映射到TargetBean. target etListTransformmed的List。
这非常有效。当我意识到我需要为另一个Target属性生成另一个List时,我的问题出现了,在SourceBeanOne中对其他属性做不同的事情。另一种自定义映射方法是不可能的,因为MapStruct无法消除歧义。这就是我结束尝试5.9的方式。基于限定符的映射方法选择。这是基于@命名生成的限定符类(以避免生成一堆@Qualified注释):
import org.mapstruct.Named;
import java.util.ArrayList;
import java.util.List;
@Named("sourceOneTransformer")
public class SourceOneExtractor {
@Named("transformerOne")
public List<String> getterMethodOne(SourceBeanOne s) {
final List<String> result = new ArrayList<>();
// simulate a transformation
result.add("one");
result.add("two");
return result;
}
@Named("transformerTwo")
public List<String> getterMethodTwo(SourceBeanOne s) {
final List<String> result = new ArrayList<>();
// simulate a transformation
result.add("three");
result.add("four");
return result;
}
}
然后我得到了这个错误:
源参数中不存在名为“target etListTransformedWelltherMethod”的属性。请显式定义源。
一开始,我不得不处理Lombok-Mapstruct问题,阅读Stack Overflow和MapStruct留档中的其他一些帖子(我可以将MapStruct与Project Lombok一起使用吗?),但一旦解决,一切都正常,Lombok注释在MapStruct之前得到正确处理。但是,以防万一,我也尝试了手工编写的构造函数、目标构建器和getter代码,结果相同,所以Lombok没有导致这种情况。我还确保我使用了正确的@Name注释(为什么@Name不起作用?)。
我真正的用例(这里的代码只是为了清晰起见而进行的简化)源代码没有任何名为“target etListTransformedWelltherMethod”的属性。
错误的最后一部分终于给了我提示:“请明确定义源代码。”查看Mapstruct源代码,我看到它正在尝试在SourceBeanOne中找到一个名为“target etListTransformedWelltherMethod”的属性。由于没有,映射是不可能的,它失败了,说“[…]明确定义源代码”。尽管5.9中没有任何示例。基于限定符的映射方法选择通过source参数指示源bean,我尝试了一下,现在它起作用了。
我在这里留下映射器的最终代码和一个测试,以供参考。限定符的代码与上面的问题相同。
映射器:
@Mapper(uses = SourceOneExtractor.class)
public interface TargetBeanMapper {
@Mapping(target = "targetListTransformed", source= "source1", qualifiedByName = {"sourceOneTransformer", "transformerOne"})
@Mapping(target = "targetListDirectMapping", source = "source1.sourceListDirectMapping")
@Mapping(target = "targetListTransformedWithAnotherMethod", source= "source1", qualifiedByName = {"sourceOneTransformer", "transformerTwo"})
@Mapping(target = "targetString", source = "source1.sourceString")
@Mapping(target = "targetInteger", source = "source2.sourceInteger")
TargetBean map (SourceBeanOne source1, SourceBeanTwo source2);
}
样本测试:
class TargetBeanMapperTest {
private static TargetBeanMapper mapper;
@BeforeAll
static void instantiateMapper() {
// instantiate mapper under test
mapper = Mappers.getMapper(TargetBeanMapper.class);
}
@Test
void map() {
final SourceBeanOne s1 = new SourceBeanOne(List.of("this", "list", "will be transformed"),
List.of("this", "list", "will be directly mapped"),
"sourceOneString");
final SourceBeanTwo s2 = new SourceBeanTwo(66);
final TargetBean t = mapper.map(s1, s2);
assertNotEquals(s1.getSourceListToTransform(),
t.getTargetListTransformed()); // check that source1 list has been transformed
assertEquals(List.of("one", "two"), t.getTargetListTransformed());
assertEquals(List.of("three", "four"),
t.getTargetListTransformedWithAnotherMethod()); // check that source1 list has been transformed
assertEquals(s1.getSourceListDirectMapping(),
t.getTargetListDirectMapping());
assertEquals(s1.getSourceString(), t.getTargetString());
assertEquals(s2.getSourceInteger(), t.getTargetInteger());
}
}