当前位置: 首页 > 知识库问答 >
问题:

ModelMapper:确保方法具有零参数并且不返回void

鞠隐水
2023-03-14

我对模型映射器进行了以下配置,以将用户类的实例转换为扩展getuserdto的实例。

    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);
    }

在注释掉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

在花费了一些时间并没有找到根本原因之后,我试图删除DTO中所有可疑的循环依赖项(我在GetExpenseDto中引用了GetExpenseDto,expenseDtoConverter的返回结果),我仍然收到相同的错误,我注释掉了map()。setReceivedExpenses(如代码中所示),并将其替换为简单的for循环。

我得到以下错误:

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

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

编辑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
}

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
}

共有1个答案

章琛
2023-03-14

您收到这些错误是因为属性映射限制了您可以在配置()中执行的操作。

在Javadoc中:

属性映射使用嵌入式域特定语言(EDSL)来定义源和目标方法和值如何相互映射。映射EDSL允许您使用引用要映射的源和目标属性的实际代码来定义映射。EDSL的用法在下面的示例中进行了演示。

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

浏览库源代码:您得到的错误是invalidSourceMethod,在ExplicitMappingVisitor中抛出,ObjectMapper使用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(",")));
        }

    }

}

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

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

因此,confiure()仅执行一次以记录映射指令,然后生成的映射代码(不是confiure()本身)被重放5次,每个对象映射一次。

如果用map()注释掉这些行。在configure()中设置thingscsvfromlist(things),然后取消注释“This will fail”(这将失败)下面的两行,您会得到:

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.

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

(A—遗留,对于Java 6/7)严格限制DSL所需的配置内容。例如,将“特殊需求”(日志记录、收集逻辑等)移动到DTO本身中的专用方法。

就你的情况而言,将这种逻辑转移到其他地方可能需要更多的工作,但想法就在那里。

请注意,文档暗示了属性ap.configure,其DSL主要用于Java6/7,但Java8和lambda现在允许优雅的解决方案,其优点是不需要字节码操作魔法。

(B--Java 8)查看其他选项,例如转换器。

这是另一个示例(使用与上面相同的数据类,并为整个类型使用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
 类似资料:
  • 问题内容: 我有这样的代码来使用读取文本文件: 它可以正常工作,但是Findbugs报告警告: NP_DEREFERENCE_OF_READLINE_VALUE:取消引用调用readLine()的结果,而不检查结果是否为null。如果没有更多的文本行要读取,则readLine()将返回null,并取消引用将生成一个null指针异常。 当我更改为时,即 该方法返回而该方法始终返回-实际上这是一个无限

  • 考虑这段代码(引用自geeksforgeeks.org,作者Tushar Roy),如果从根到叶的路径具有总和为指定值的键,它会计算true或false: 在这段代码中,作者在对变量ans的赋值中使用了逻辑OR运算符,以避免用false覆盖true返回。我已将代码重构为: 尽管在这种情况下使用临时变量和/或逻辑OR运算符显然可以有效地防止递归返回的覆盖,但在递归调用中携带值的最佳方法是什么? 编辑

  • 当我用字典参数插入表的值,并且发生了一些异常时,npgsql不会清晰地返回SQL查询。示例: 数据库异常:22021:编码“UTF8”的字节序列无效:0x00。Query==>插入foo值($1) 在使用NPGSQL2.2.5之前,我遇到了如下错误: 数据库异常:22021:编码“UTF8”的字节序列无效:0x00。Query==>insert into foo值('null\0 null')

  • 我正在尝试编写一个ArchUnit测试规则,它应该确保使用注释的接口具有使用注释注释的方法参数。像这样: 界面如下所示: 但测试失败,出现如下错误:

  • 我正在尝试使用数据库的第一个图像设置ImageView。由于某种原因,它不起作用,我不知道为什么。方法loadImage在不同的类中调用。 我还试着用一个单独的方法设置图像,但我需要在创建整个舞台场景等的同时调用它,即使从第二个方法那样调用它,它也不起作用。 这是持有setImage的类 当调用时,设置图像会给我一个null值,imageview的默认值为null,但实际上我正在加载一个图像,甚至

  • 本文向大家介绍Nginx中定义404页面并且返回404状态码的正确方法,包括了Nginx中定义404页面并且返回404状态码的正确方法的使用技巧和注意事项,需要的朋友参考一下 前几天,一朋友出程序出问题却怎么查都没看出问题,于是让我帮它看看。其实它是ajax请求了很多个模板,然后把模板写到页面中。关键是所有请求的页面都是200正常状态码返回,表面上看没什么问题,实际上有些请求虽然返回200状态码,