提问者:小点点

无法根据限定符进行作品映射方法选择


我使用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”的属性。


共1个答案

匿名用户

错误的最后一部分终于给了我提示:“请明确定义源代码。”查看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());
    }
}