微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

ModelMapper - 定义显式映射时出现非法 SourceGetter 错误

如何解决ModelMapper - 定义显式映射时出现非法 SourceGetter 错误

第一部分

我使用 Java modelmapper 库 (http://modelmapper.org/) 来管理实体和 DTO 之间的映射。我有一个联系人(实体)和一个 ContactView (DTO)。 我在 ContactView 中有一个字符串字段,它在 Contact 中不存在,称为“type”。 它的值应该只是实体的子类的名称。 我试图像这样制作这个自定义映射:

modelmapper.typeMap(Contact.class,ContactView.class).addMappings(mapper -> {   
   mapper.map(src -> src.getClass().getSimpleName(),ContactView::setType);
});

我在以下位置收到编译错误: mapper.map(src -> src.getClass().getSimpleName(),ContactView::setType);

定义了非法的 SourceGetter

1 处错误 org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.2.jar:5.3.2] 在 org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.2.jar:5.3.2] ...省略了33个常用框架

我什至尝试使用转换器,结果相同:

modelmapper.typeMap(Contact.class,ContactView.class).addMappings(mapper -> {
  Converter<Class,String> toName = ctx -> ctx.getSource() == null ? null : ctx.getSource().getSimpleName();
  mapper.using(toName).map(Contact::getClass,ContactView::setType);
});

你知道如何解决这个问题吗?

第 2 部分

按照建议的答案,我尝试向 modelmapper 添加一个转换器类。这是我配置 modelmapper Bean 的地方:

@Configuration
public class Mapper {
    @Autowired
    private ContactTypeRepository contactTypeRepository;

    @Bean
    public modelmapper getMapper() {
        modelmapper modelmapper = new modelmapper();
        modelmapper.getConfiguration()
                .setMatchingStrategy(MatchingStrategies.STRICT);

        modelmapper.typeMap(ContactTag.class,ReferenceEntityView.class).addMappings(mapper -> {
            mapper.map(src -> src.getTag().getCode(),ReferenceEntityView::setCode);
            mapper.map(src -> src.getTag().getValue(),ReferenceEntityView::setValue);
        });

        modelmapper.typeMap(Person.class,PersonView.class).addMappings(mapper -> {
            mapper.skip(PersonView::setName);
            mapper.map(Person::getName,PersonView::setLastName);
        });

        modelmapper.addConverter(new ContactConverter());

        return modelmapper;
    }

    class ContactConverter implements Converter<Contact,ContactView>  {
        private modelmapper localmapper = new modelmapper();

        @Override
        public ContactView convert(MappingContext<Contact,ContactView> context) {
            Contact contact = context.getSource();
            ContactView contactView = localmapper.map(contact,ContactView.class);
            ContactType contactType = contactTypeRepository.getByCode(context.getSource().getClass().getSimpleName().toLowerCase());
            contactView.setType(localmapper.map(contactType,ReferenceEntityView.class));
            return contactView;
        }
    }
}

这是我使用 modelmapper Bean 生成 DTO 的地方:

@RestController
@RequestMapping(value = "/contacts")
public class ContactController {
    @Autowired
    private ContactRepository contactRepository;
    @Autowired
    private modelmapper modelmapper;
    
    @GetMapping(value = "/{id}")
    @ResponseStatus(HttpStatus.OK)
    public ContactView findById(@PathVariable("id") Long id){
        Contact c = contactRepository.getone(id);
        ContactView cv = modelmapper.map(c,ContactView.class);
        return cv;
    }
}

由于某种原因,Converter 的 convert 方法没有被调用,ContactView 对象的“type”字段为空。 modelmapper Bean 上的其他映射工作正常。

解决方法

它的发生是因为 ModelMapper 的实现

 public boolean isValid(M member) {
  return !Modifier.isStatic(member.getModifiers()) && !member.isSynthetic();
}

isSynthetic 方法的文档说

如果该成员是由编译器引入的,则返回真;返回 否则为假。返回: true 当且仅当该成员是 由编译器引入。

我想这就是它失败的原因。

对于类似的情况,我们引入了一个特定的映射器类,使用 modelMapper 作为基础映射器并设置其他字段:

 class ContactMapper{
 ...
 public ContactView toView(Contact contact){
 ContactView contactView = modelMapper.map(contact,ContactView.class);
 contactView.setType(contact.getClass().getSimpleName());
 return contactView;
 }

为了使其与整体映射一致,您可以将其定义为转换器并将其注册到您的映射中

class ContactConverter implements Converter<Contact,ContactView>  {
        private final ModelMapper localMapper = new ModelMapper();

        @Override
        public ContactView convert(MappingContext<Contact,ContactView> context) {
            Contact contact = context.getSource();
            ContactView contactView = localMapper.map(contact,ContactView.class);
            contactView.setType(contact.getClass().getSimpleName());
            return contactView;
        }
    }

    ModelMapper modelMapper = new ModelMapper();
    modelMapper.addConverter(new ContactConverter());
,

试试我的图书馆beanknife。这是一个注释处理器。这意味着 jdk 会在您编译项目之前为您生成类。所以它在编译时完成工作。您可以像使用自己编写的任何其他类一样使用生成的类。你可以看到生成的类的来源,所以没有更多的魔法。 该库能够为您生成 DTO 类。您不需要更改原始类。除了在原来的类上配置注解,还可以选择新建一个配置类,并在其上配置注解。该库支持复制和继承原始类的所有属性,并在此基础上删除修改或添加属性。对于您的问题:

// this will generate a DTO class named "ContactView". 
// (This is the default name which just append 'View') 
// You can change this name using genName attribute.
@ViewOf(value=Contact.class,includePattern = ".*")
public class ContactViewConfigure {
    // Add a new property. 
    // Make sure there is no property named 'type' already in Contact. 
    // Or you need use @OverrideViewProperty
    // There are multi way to add new property.
    // In this way,you use a public static method accept the original class instance as the unique argument. 
    // The new property name is decide by the attribute 'type' of @NewViewProperty. 
    // So the method name is not important.
    @NewViewProperty("type")
    public static String type(Contact contact) {
        return contact.getClass().getSimpleName()
    }
}

// This is the generated class.
public class ContactView {
    // other properties
    ...
    private String type;
    // getters and setters (By default only getters are generated)
    ...
    // many constructors
    ...
    public static ContactView read(Contact source) {
        ContactView out = new ContactView();
        // initialize other properties
        ...
        out.type = ContactViewConfigure.type(source);
        // initialize other properties
        ...
        return out;
    }

    // other read method,such as read list,set and map.
    ...
    // other generated methods
    ...
}

// use like this.
Contact contact = ...
ContactView dto = ContactView.read(contact);

在某些情况下,beanknife 比 ModelMapper 强大得多。例如,如果发生错误,您可以检查生成的类的来源(通常位于 /target/generated-source/annotations 中,在 IDE 上可能会有所不同),并查看原因。如果真的是bug,可以提交问题到github,我会尽快处理。

还有更多examples

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。