如何解决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 举报,一经查实,本站将立刻删除。