ModelMapper:确保该方法的参数为零,并且不返回void

Ari*_*deh 8 java spring modelmapper

我为模型映射器配置了以下配置,以将User类的实例转换为实例ExtendedGetUserDto.

    public ExtendedGetUserDto convertToExtendedDto(User user) {
        PropertyMap<User, ExtendedGetUserDto> userMap = new PropertyMap<User, ExtendedGetUserDto>() {
            protected void configure() {
                map().setDescription(source.getDescription());
                map().setId(source.getId());
//              map().setReceivedExpenses(
//                      source.getReceivedExpenses()
//                              .stream()
//                              .map(expense -> expenseDtoConverter.convertToDto(expense))
//                              .collect(Collectors.toSet())
//                      );
                Set<GetInvitationDto> result = new HashSet<GetInvitationDto>();
                for (Invitation inv: source.getReceivedInvitations()) {
                    System.out.println("HELLO");
                    //result.add(null);
                }
                //map().setReceivedInvitations(result);
            }
        };
        modelMapper.addMappings(userMap);
        return modelMapper.map(user, ExtendedGetUserDto.class);
    }
Run Code Online (Sandbox Code Playgroud)

在评论之前setReceivedExpense我收到了这个错误:

org.modelmapper.ConfigurationException: ModelMapper configuration errors:

1) Invalid source method java.util.stream.Stream.map(). Ensure that method has zero parameters and does not return void.

2) Invalid source method java.util.stream.Stream.collect(). Ensure that method has zero parameters and does not return void.

2 errors
Run Code Online (Sandbox Code Playgroud)

花了一些时间而没有找到根本原因后,我试图删除DTO中所有可疑的循环依赖(我已经GetUserDto引用了GetExpenseDto,返回结果expenseDtoConverter)我仍然收到相同的错误,我注释掉了map().setReceivedExpenses(正如你在代码)并用简单的for循环替换它.

我收到以下错误:

1) Invalid source method java.io.PrintStream.println(). Ensure that method has zero parameters and does not return void.
Run Code Online (Sandbox Code Playgroud)

为什么我会收到这些错误?

编辑1

User.java

@Entity
@Table(name="User")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="id")
    private long id;

    @Column(name = "name")
    private String name;

    @Size(min=15, max=15)
    @Column(name="image_id")
    private String imageId;

    @Size(max=100)
    @Column(name="description")
    private String description;

    @OneToMany(mappedBy="admin")
    private Set<Group> ownedGroups;

    @ManyToMany(mappedBy="members")
    private Set<Group> memberGroups;

    @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="owner")
    private Set<Expense> ownedExpenses;

    @ManyToMany(cascade = CascadeType.REFRESH, fetch=FetchType.EAGER)
    private Set<Expense> receivedExpenses;

    @OneToMany(cascade=CascadeType.ALL)
    private Set<Invitation> ownedInvitations;

    @OneToMany(cascade=CascadeType.ALL)
    private Set<Invitation> receivedInvitations;
    //setters and getters for attributes
}
Run Code Online (Sandbox Code Playgroud)

ExtendedGetUserDto.java

public class ExtendedGetUserDto extends GetUserDto {

    private static final long serialVersionUID = 1L;

    private Set<GetInvitationDto> receivedInvitations;
    private Set<GetExpenseDto> receivedExpenses;
    private Set<GetExpenseDto> ownedExpenses;
    private Set<GetGroupDto> ownedGroups;
    private Set<GetGroupDto> memberGroups;
    //setters and getters for attributes
}
Run Code Online (Sandbox Code Playgroud)

Hug*_* M. 20

您收到这些错误是因为PropertyMap限制了您可以在其中执行的操作configure().

Javadoc中:

PropertyMap使用嵌入式域特定语言(EDSL)来定义源和目标方法和值如何相互映射.Mapping EDSL允许您使用引用要映射的源和目标属性的实际代码来定义映射.EDSL的用法在以下实施例中说明.

从技术上讲,它涉及字节码分析,操作和代理,并且它需要适合此EDSL的Java方法调用.这个聪明的技巧允许ModelMapper记录您的映射指令,并随意重放它们.

要了解库源代码:您得到的错误是invalidSourceMethod,在ExplicitMappingVisitor中抛出此处,其中ObjectMapper configure使用ASM库访问并检测方法的代码.

以下示例是一个独立的可运行示例,应该有助于澄清.我邀请你复制它ModelMapperTest.java并实际运行它,然后在里面切换注释configure()以重现错误:

import org.modelmapper.ModelMapper;
import org.modelmapper.PropertyMap;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ModelMapperTest {

    public static void main(String[] args) {
        PropertyMap<Foo, FooDTO> propertyMap = new PropertyMap<Foo, FooDTO>() {
            protected void configure() {
                /* This is executed exactly ONCE, to "record" the mapping instructions.
                 * The bytecode of this configure() method is analyzed to produce new mapping code,
                 * a new dynamically-generated class with a method that will basically contain the same instructions
                 * that will be "replayed" each time you actually map an object later.
                 * But this can only work if the instructions are simple enough (ie follow the DSL).
                 * If you add non-compliant code here, it will break before "configure" is invoked.
                 * Non-compliant code is supposedly anything that does not follow the DSL.
                 * In practice, the framework only tracks what happens to "map()" and "source", so
                 * as long as print instructions do not access the source or target data (like below),
                 * the framework will ignore them, and they are safe to leave for debug. */
                System.out.println("Entering configure()");
                // This works
                List<String> things = source.getThings();
                map().setThingsCSVFromList(things);
                // This would fail (not because of Java 8 code, but because of non-DSL code that accesses the data)
                // String csv = things.stream().collect(Collectors.joining(","));
                // map().setThingsCSV(csv);
                System.out.println("Exiting configure()");
            }
        };
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.addMappings(propertyMap);
        for (int i=0; i<5; i++) {
            Foo foo = new Foo();
            foo.setThings(Arrays.asList("a"+i, "b"+i, "c"+i));
            FooDTO dto = new FooDTO();
            modelMapper.map(foo, dto); // The configure method is not re-executed, but the dynamically generated mapper method is.
            System.out.println(dto.getThingsCSV());
        }
    }

    public static class Foo {

        List<String> things;

        public List<String> getThings() {
            return things;
        }

        public void setThings(List<String> things) {
            this.things = things;
        }

    }

    public static class FooDTO {

        String thingsCSV;

        public String getThingsCSV() {
            return thingsCSV;
        }

        public void setThingsCSV(String thingsCSV) {
            this.thingsCSV = thingsCSV;
        }

        public void setThingsCSVFromList(List<String> things) {
            setThingsCSV(things.stream().collect(Collectors.joining(",")));
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

如果你按原样执行它,你会得到:

Entering configure()
Exiting configure()
a0,b0,c0
a1,b1,c1
a2,b2,c2
a3,b3,c3
a4,b4,c4
Run Code Online (Sandbox Code Playgroud)

因此,configure()只执行一次记录映射指令,然后生成的映射代码(不是configure()自身)重放 5次,每次对象映射一次.

如果你注释掉map().setThingsCSVFromList(things)内部的行configure(),然后取消注释"这会失败"下面的2行,你得到:

Exception in thread "main" org.modelmapper.ConfigurationException: ModelMapper configuration errors:

1) Invalid source method java.util.stream.Stream.collect(). Ensure that method has zero parameters and does not return void.
Run Code Online (Sandbox Code Playgroud)

简而言之,您无法直接在其中执行复杂的自定义逻辑PropertyMap.configure(),但您可以调用这样做的方法.这是因为框架只需要检测处理纯映射逻辑(即DSL)的字节码部分,它不关心这些方法中发生了什么.

(A - legacy,for Java 6/7)严格限制configureDSL所要求的内容.例如,将您的"特殊需求"(记录,收集逻辑等)移动到DTO本身的专用方法.

在你的情况下,将这种逻辑转移到其他地方可能会有更多的工作,但这个想法就在那里.

请注意文档暗示PropertyMap.configure并且其DSL主要用于Java 6/7,但Java 8和lambdas现在允许优雅的解决方案,其优点是不需要字节码操作魔法.

(B - Java 8)查看其他选项,例如Converter.

这是另一个例子(使用与上面相同的数据类,以及Converter整个类型的一个,因为这更适合我的例子,但你可以按属性执行):

    Converter<Foo, FooDTO> converter = context -> {
        FooDTO dto = new FooDTO();
        dto.setThingsCSV(
                context.getSource().getThings().stream()
                        .collect(Collectors.joining(",")));
        return dto;
    };
    ModelMapper modelMapper = new ModelMapper();
    modelMapper.createTypeMap(Foo.class, FooDTO.class)
            .setConverter(converter);
    Foo foo = new Foo();
    foo.setThings(Arrays.asList("a", "b", "c"));
    FooDTO dto = modelMapper.map(foo, FooDTO.class);
    System.out.println(dto.getThingsCSV()); // a,b,c
Run Code Online (Sandbox Code Playgroud)