1. 概述
Dozer是一个Java Bean到Java Bean的映射工具,它能递归地将一个对象的属性逐个复制到另一个对象中。
该库不仅支持同名属性的映射,还能自动处理类型转换——当源对象和目标对象的属性类型不同时。
大多数转换场景开箱即用,但Dozer也允许通过XML配置自定义转换规则。
2. 简单示例
先看个最基础的例子:假设源对象和目标对象的属性名完全相同。这是Dozer最简单的映射场景:
public class Source {
private String name;
private int age;
public Source() {}
public Source(String name, int age) {
this.name = name;
this.age = age;
}
// 标准getter和setter方法
}
目标类Dest.java:
public class Dest {
private String name;
private int age;
public Dest() {}
public Dest(String name, int age) {
this.name = name;
this.age = age;
}
// 标准getter和setter方法
}
务必包含无参构造函数,因为Dozer底层依赖反射机制。为提升性能,我们创建全局单例的mapper:
DozerBeanMapper mapper;
@Before
public void before() throws Exception {
mapper = new DozerBeanMapper();
}
现在测试将Source对象映射到Dest对象:
@Test
public void givenSourceObjectAndDestClass_whenMapsSameNameFieldsCorrectly_
thenCorrect() {
Source source = new Source("Baeldung", 10);
Dest dest = mapper.map(source, Dest.class);
assertEquals(dest.getName(), "Baeldung");
assertEquals(dest.getAge(), 10);
}
映射后,Dest对象会包含所有与Source对象同名字段的值。也可以直接传入目标对象实例:
@Test
public void givenSourceObjectAndDestObject_whenMapsSameNameFieldsCorrectly_
thenCorrect() {
Source source = new Source("Baeldung", 10);
Dest dest = new Dest();
mapper.map(source, dest);
assertEquals(dest.getName(), "Baeldung");
assertEquals(dest.getAge(), 10);
}
3. Maven配置
在pom.xml中添加依赖:
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.5.1</version>
</dependency>
最新版本可在此处获取:Maven中央仓库
4. 数据类型转换示例
Dozer能自动处理属性类型不同的场景。当遇到类型不匹配时,映射引擎会自动执行类型转换。
看个实际例子:
public class Source2 {
private String id;
private double points;
public Source2() {}
public Source2(String id, double points) {
this.id = id;
this.points = points;
}
// 标准getter和setter方法
}
目标类:
public class Dest2 {
private int id;
private int points;
public Dest2() {}
public Dest2(int id, int points) {
super();
this.id = id;
this.points = points;
}
// 标准getter和setter方法
}
注意属性名相同但类型不同:源类中id是String,points是double;目标类中两者都是int。
测试自动转换:
@Test
public void givenSourceAndDestWithDifferentFieldTypes_
whenMapsAndAutoConverts_thenCorrect() {
Source2 source = new Source2("320", 15.2);
Dest2 dest = mapper.map(source, Dest2.class);
assertEquals(dest.getId(), 320);
assertEquals(dest.getPoints(), 15);
}
输入String和double,输出自动转为int,简单粗暴!
5. 基于XML的自定义映射
实际开发中,源对象和目标对象的属性名往往不同。Dozer允许通过XML配置自定义映射规则。
假设需要将法语系统的Personne对象映射到英语系统的Person对象:
public class Person {
private String name;
private String nickname;
private int age;
public Person() {}
public Person(String name, String nickname, int age) {
super();
this.name = name;
this.nickname = nickname;
this.age = age;
}
// 标准getter和setter方法
}
法语对象Personne:
public class Personne {
private String nom;
private String surnom;
private int age;
public Personne() {}
public Personne(String nom, String surnom, int age) {
super();
this.nom = nom;
this.surnom = surnom;
this.age = age;
}
// 标准getter和setter方法
}
创建XML映射文件dozer_mapping.xml:
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozer.sourceforge.net
http://dozer.sourceforge.net/schema/beanmapping.xsd">
<mapping>
<class-a>com.baeldung.dozer.Personne</class-a>
<class-b>com.baeldung.dozer.Person</class-b>
<field>
<a>nom</a>
<b>name</b>
</field>
<field>
<a>surnom</a>
<b>nickname</b>
</field>
</mapping>
</mappings>
关键点:
- 根元素
<mappings>
包含多个<mapping>
子元素 - 每个
<mapping>
定义一对类的映射关系 <field>
指定源属性和目标属性的对应关系- 同名属性无需配置(如age),Dozer会自动映射
添加辅助方法加载映射文件:
public void configureMapper(String... mappingFileUrls) {
mapper.setMappingFiles(Arrays.asList(mappingFileUrls));
}
测试自定义映射:
@Test
public void givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_
whenMaps_thenCorrect() {
configureMapper("dozer_mapping.xml");
Personne frenchAppPerson = new Personne("Sylvester Stallone", "Rambo", 70);
Person englishAppPerson = mapper.map(frenchAppPerson, Person.class);
assertEquals(englishAppPerson.getName(), frenchAppPerson.getNom());
assertEquals(englishAppPerson.getNickname(), frenchAppPerson.getSurnom());
assertEquals(englishAppPerson.getAge(), frenchAppPerson.getAge());
}
Dozer支持双向映射,无需额外配置:
@Test
public void givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_
whenMapsBidirectionally_thenCorrect() {
configureMapper("dozer_mapping.xml");
Person englishAppPerson = new Person("Dwayne Johnson", "The Rock", 44);
Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);
assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
assertEquals(frenchAppPerson.getSurnom(),englishAppPerson.getNickname());
assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}
映射文件也可放在类路径外,使用file:
前缀:
- Windows:
file:E:\\dozer_mapping.xml
- Linux:
file:/home/dozer_mapping.xml
- Mac:
file:/Users/me/dozer_mapping.xml
@Test
public void givenMappingFileOutsideClasspath_whenMaps_thenCorrect() {
configureMapper("file:E:\\dozer_mapping.xml");
Person englishAppPerson = new Person("Marshall Bruce Mathers III","Eminem", 43);
Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);
assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
assertEquals(frenchAppPerson.getSurnom(),englishAppPerson.getNickname());
assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}
6. 通配符与XML高级定制
创建第二个映射文件dozer_mapping2.xml:
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozer.sourceforge.net
http://dozer.sourceforge.net/schema/beanmapping.xsd">
<mapping wildcard="false">
<class-a>com.baeldung.dozer.Personne</class-a>
<class-b>com.baeldung.dozer.Person</class-b>
<field>
<a>nom</a>
<b>name</b>
</field>
<field>
<a>surnom</a>
<b>nickname</b>
</field>
</mapping>
</mappings>
新增wildcard="false"
属性:
- 默认
wildcard="true"
:自动映射所有同名属性 - 设为
false
:仅映射显式指定的属性
测试效果:
@Test
public void givenSrcAndDest_whenMapsOnlySpecifiedFields_thenCorrect() {
configureMapper("dozer_mapping2.xml");
Person englishAppPerson = new Person("Shawn Corey Carter","Jay Z", 46);
Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);
assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
assertEquals(frenchAppPerson.getSurnom(),englishAppPerson.getNickname());
assertEquals(frenchAppPerson.getAge(), 0); // 未映射的属性保持默认值
}
7. 基于注解的自定义映射
对于简单场景且能修改源码时,注解比XML更简洁。创建Person2和Personne2类:
在源对象Personne2的getter方法上添加@Mapping
注解:
@Mapping("name")
public String getNom() {
return nom;
}
@Mapping("nickname")
public String getSurnom() {
return surnom;
}
测试注解映射:
@Test
public void givenAnnotatedSrcFields_whenMapsToRightDestField_thenCorrect() {
Person2 englishAppPerson = new Person2("Jean-Claude Van Damme", "JCVD", 55);
Personne2 frenchAppPerson = mapper.map(englishAppPerson, Personne2.class);
assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
assertEquals(frenchAppPerson.getSurnom(), englishAppPerson.getNickname());
assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}
双向映射同样支持:
@Test
public void givenAnnotatedSrcFields_whenMapsToRightDestFieldBidirectionally_
thenCorrect() {
Personne2 frenchAppPerson = new Personne2("Jason Statham", "transporter", 49);
Person2 englishAppPerson = mapper.map(frenchAppPerson, Person2.class);
assertEquals(englishAppPerson.getName(), frenchAppPerson.getNom());
assertEquals(englishAppPerson.getNickname(), frenchAppPerson.getSurnom());
assertEquals(englishAppPerson.getAge(), frenchAppPerson.getAge());
}
8. 基于API的自定义映射
另一种无XML的方案是使用API映射。通过BeanMappingBuilder
以代码方式定义映射:
BeanMappingBuilder builder = new BeanMappingBuilder() {
@Override
protected void configure() {
mapping(Person.class, Personne.class)
.fields("name", "nom")
.fields("nickname", "surnom");
}
};
将构建器添加到mapper:
@Test
public void givenApiMapper_whenMaps_thenCorrect() {
mapper.addMapping(builder);
Personne frenchAppPerson = new Personne("Sylvester Stallone", "Rambo", 70);
Person englishAppPerson = mapper.map(frenchAppPerson, Person.class);
assertEquals(englishAppPerson.getName(), frenchAppPerson.getNom());
assertEquals(englishAppPerson.getNickname(), frenchAppPerson.getSurnom());
assertEquals(englishAppPerson.getAge(), frenchAppPerson.getAge());
}
双向映射同样生效:
@Test
public void givenApiMapper_whenMapsBidirectionally_thenCorrect() {
mapper.addMapping(builder);
Person englishAppPerson = new Person("Sylvester Stallone", "Rambo", 70);
Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);
assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
assertEquals(frenchAppPerson.getSurnom(), englishAppPerson.getNickname());
assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}
排除特定字段:
BeanMappingBuilder builderMinusAge = new BeanMappingBuilder() {
@Override
protected void configure() {
mapping(Person.class, Personne.class)
.fields("name", "nom")
.fields("nickname", "surnom")
.exclude("age");
}
};
测试排除效果:
@Test
public void givenApiMapper_whenMapsOnlySpecifiedFields_thenCorrect() {
mapper.addMapping(builderMinusAge);
Person englishAppPerson = new Person("Sylvester Stallone", "Rambo", 70);
Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);
assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
assertEquals(frenchAppPerson.getSurnom(), englishAppPerson.getNickname());
assertEquals(frenchAppPerson.getAge(), 0); // 被排除的属性保持默认值
}
9. 自定义转换器
当需要特殊类型转换时(如时间戳转ISO日期),可注册自定义转换器。
创建源类Personne3(时间戳格式):
public class Personne3 {
private String name;
private long dtob;
public Personne3(String name, long dtob) {
super();
this.name = name;
this.dtob = dtob;
}
// 标准getter和setter方法
}
目标类Person3(ISO日期格式):
public class Person3 {
private String name;
private String dtob;
public Person3(String name, String dtob) {
super();
this.name = name;
this.dtob = dtob;
}
// 标准getter和setter方法
}
实现自定义转换器:
public class MyCustomConvertor implements CustomConverter {
@Override
public Object convert(Object dest, Object source, Class<?> arg2, Class<?> arg3) {
if (source == null)
return null;
if (source instanceof Personne3) {
Personne3 person = (Personne3) source;
Date date = new Date(person.getDtob());
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
String isoDate = format.format(date);
return new Person3(person.getName(), isoDate);
} else if (source instanceof Person3) {
Person3 person = (Person3) source;
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
Date date = format.parse(person.getDtob());
long timestamp = date.getTime();
return new Personne3(person.getName(), timestamp);
}
}
}
在XML中注册转换器(dozer_custom_convertor.xml):
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozer.sourceforge.net
http://dozer.sourceforge.net/schema/beanmapping.xsd">
<configuration>
<custom-converters>
<converter type="com.baeldung.dozer.MyCustomConvertor">
<class-a>com.baeldung.dozer.Personne3</class-a>
<class-b>com.baeldung.dozer.Person3</class-b>
</converter>
</custom-converters>
</configuration>
</mappings>
测试转换效果:
@Test
public void givenSrcAndDestWithDifferentFieldTypes_whenAbleToCustomConvert_
thenCorrect() {
configureMapper("dozer_custom_convertor.xml");
String dateTime = "2007-06-26T21:22:39Z";
long timestamp = new Long("1182882159000");
Person3 person = new Person3("Rich", dateTime);
Personne3 person0 = mapper.map(person, Personne3.class);
assertEquals(timestamp, person0.getDtob());
}
双向转换测试:
@Test
public void givenSrcAndDestWithDifferentFieldTypes_
whenAbleToCustomConvertBidirectionally_thenCorrect() {
configureMapper("dozer_custom_convertor.xml");
String dateTime = "2007-06-26T21:22:39Z";
long timestamp = new Long("1182882159000");
Personne3 person = new Personne3("Rich", timestamp);
Person3 person0 = mapper.map(person, Person3.class);
assertEquals(dateTime, person0.getDtob());
}
10. 总结
本文全面介绍了Dozer映射库的核心功能,包括:
- 基础属性映射与自动类型转换
- XML/注解/API三种自定义映射方式
- 通配符控制与字段排除
- 自定义转换器实现复杂类型转换
完整示例代码可在GitHub项目中获取。掌握这些技巧,能轻松应对Java Bean间的各种映射需求,避免手动赋值的繁琐操作。