1. 概述
本文将介绍 JMapper —— 一个高性能且易于使用的 Java 对象映射框架。
我们将探讨 JMapper 的三种配置方式(API、注解、XML),如何实现自定义字段转换,以及处理一对多的关系映射(Relational Mapping)场景。对于经常写 DTO 转换、踩过 BeanUtils
性能坑的同学来说,JMapper 是个简单粗暴的替代方案。
2. Maven 依赖
首先,在 pom.xml
中引入 JMapper 核心依赖:
<dependency>
<groupId>com.googlecode.jmapper-framework</groupId>
<artifactId>jmapper-core</artifactId>
<version>1.6.0.1</version>
</dependency>
✅ 推荐使用最新稳定版,避免早期版本中某些注解解析的 Bug。
3. 源对象与目标对象
在开始配置前,先定义两个简单的 Bean 类:
源对象 User
:
public class User {
private long id;
private String email;
private LocalDate birthDate;
// 构造函数、getter、setter 省略
}
目标对象 UserDto
:
public class UserDto {
private long id;
private String username;
}
我们的目标是:将 User
中的字段映射到 UserDto
,比如 email → username
。
JMapper 支持三种配置方式:
- ✅ API 配置:灵活,适合动态场景
- ✅ 注解配置:简洁,推荐日常使用
- ✅ XML 配置:集中管理,适合复杂项目
下面逐一演示。
4. 使用 API 配置
API 方式无需修改源/目标类,所有映射规则通过 JMapperAPI
构建,灵活性最高。
@Test
public void givenUser_whenUseApi_thenConverted(){
JMapperAPI jmapperApi = new JMapperAPI()
.add(mappedClass(UserDto.class)
.add(attribute("id").value("id"))
.add(attribute("username").value("email")));
JMapper<UserDto, User> userMapper = new JMapper<>
(UserDto.class, User.class, jmapperApi);
User user = new User(1L, "john.doe@example.com", LocalDate.of(1980,8,20));
UserDto result = userMapper.getDestination(user);
assertEquals(user.getId(), result.getId());
assertEquals(user.getEmail(), result.getUsername());
}
📌 关键点:
mappedClass()
指定目标类attribute().value()
定义字段映射关系- 最后通过
getDestination()
执行映射
⚠️ 适合需要运行时动态构建映射规则的场景,但代码略 verbose。
5. 使用注解配置
通过 @JMap
注解直接在目标类上声明映射规则,最简洁的方式。
public class UserDto {
@JMap
private long id;
@JMap("email")
private String username;
}
测试代码:
@Test
public void givenUser_whenUseAnnotation_thenConverted(){
JMapper<UserDto, User> userMapper = new JMapper<>(UserDto.class, User.class);
User user = new User(1L, "john.doe@example.com", LocalDate.of(1980,8,20));
UserDto result = userMapper.getDestination(user);
assertEquals(user.getId(), result.getId());
assertEquals(user.getEmail(), result.getUsername());
}
📌 说明:
@JMap
无参数时,默认映射同名字段- 有参数时,指定源字段名(如
"email"
→username
)
✅ 推荐日常开发使用,代码清晰,维护成本低。
6. 使用 XML 配置
将映射规则集中写在 XML 文件中,适合需要统一管理或配置复杂的项目。
user_jmapper.xml
内容:
<jmapper>
<class name="com.example.jmapper.UserDto">
<attribute name="id">
<value name="id"/>
</attribute>
<attribute name="username">
<value name="email"/>
</attribute>
</class>
</jmapper>
使用方式:
@Test
public void givenUser_whenUseXml_thenConverted(){
JMapper<UserDto, User> userMapper = new JMapper<>
(UserDto.class, User.class, "user_jmapper.xml");
User user = new User(1L, "john.doe@example.com", LocalDate.of(1980,8,20));
UserDto result = userMapper.getDestination(user);
assertEquals(user.getId(), result.getId());
assertEquals(user.getEmail(), result.getUsername());
}
📌 提示:XML 也可作为字符串传入,适用于配置动态生成的场景。
7. 全局映射(Global Mapping)
当源和目标类中大部分字段名相同时,可启用全局映射,自动匹配同名字段,减少重复配置。
7.1 使用 API
@Test
public void givenUser_whenUseApiGlobal_thenConverted() {
JMapperAPI jmapperApi = new JMapperAPI()
.add(mappedClass(UserDto1.class).add(global())) ;
JMapper<UserDto1, User> userMapper1 = new JMapper<>
(UserDto1.class, User.class, jmapperApi);
User user = new User(1L, "john.doe@example.com", LocalDate.of(1980,8,20));
UserDto1 result = userMapper1.getDestination(user);
assertEquals(user.getId(), result.getId());
assertEquals(user.getEmail(), result.getEmail());
}
7.2 使用注解
在类上添加 @JGlobalMap
:
@JGlobalMap
public class UserDto1 {
private long id;
private String email;
}
测试:
@Test
public void whenUseGlobalMapAnnotation_thenConverted(){
JMapper<UserDto1, User> userMapper = new JMapper<>(UserDto1.class, User.class);
User user = new User(1L, "john.doe@example.com", LocalDate.of(1980,8,20));
UserDto1 result = userMapper.getDestination(user);
assertEquals(user.getId(), result.getId());
assertEquals(user.getEmail(), result.getEmail());
}
7.3 使用 XML
<jmapper>
<class name="com.example.jmapper.UserDto1">
<global/>
</class>
</jmapper>
使用方式同前,传入 XML 文件名即可。
✅ 全局映射 + 局部覆盖(如 @JMap
)是实际项目中的常见组合。
8. 自定义转换
当字段需要复杂逻辑转换(如日期转年龄),可使用 @JMapConversion
。
目标类新增 age
字段:
public class UserDto {
@JMap
private long id;
@JMap("email")
private String username;
@JMap("birthDate")
private int age;
@JMapConversion(from = {"birthDate"}, to = {"age"})
public int conversion(LocalDate birthDate) {
return Period.between(birthDate, LocalDate.now()).getYears();
}
}
测试:
@Test
public void whenUseAnnotationExplicitConversion_thenConverted(){
JMapper<UserDto, User> userMapper = new JMapper<>(UserDto.class, User.class);
User user = new User(1L, "john.doe@example.com", LocalDate.of(1980,8,20));
UserDto result = userMapper.getDestination(user);
assertEquals(user.getId(), result.getId());
assertEquals(user.getEmail(), result.getUsername());
assertTrue(result.getAge() > 0);
}
📌 注意:
from
和to
必须与字段注解中的源/目标字段一致- 方法必须是
public
,且参数/返回值类型匹配
⚠️ 踩坑提醒:转换方法不能是 static,否则 JMapper 无法反射调用。
9. 关系映射(Relational Mapping)
适用于一个源对象映射多个不同目标类型的场景。使用 RelationalJMapper
,支持一对多映射。
模型定义
源对象:
public class User {
private long id;
private String email;
}
两个目标对象:
public class UserDto1 {
private long id;
private String username;
}
public class UserDto2 {
private long id;
private String email;
}
9.1 使用 API
@Test
public void givenUser_whenUseApi_thenConverted(){
JMapperAPI jmapperApi = new JMapperAPI()
.add(mappedClass(User.class)
.add(attribute("id")
.value("id")
.targetClasses(UserDto1.class, UserDto2.class))
.add(attribute("email")
.targetAttributes("username", "email")
.targetClasses(UserDto1.class, UserDto2.class)));
RelationalJMapper<User> relationalMapper = new RelationalJMapper<>(User.class, jmapperApi);
User user = new User(1L, "john.doe@example.com");
UserDto1 result1 = relationalMapper.oneToMany(UserDto1.class, user);
UserDto2 result2 = relationalMapper.oneToMany(UserDto2.class, user);
assertEquals(user.getId(), result1.getId());
assertEquals(user.getEmail(), result1.getUsername());
assertEquals(user.getId(), result2.getId());
assertEquals(user.getEmail(), result2.getEmail());
}
9.2 使用注解
在源类上标注目标类型:
public class User {
@JMap(classes = {UserDto1.class, UserDto2.class})
private long id;
@JMap(attributes = {"username", "email"},
classes = {UserDto1.class, UserDto2.class})
private String email;
}
使用:
@Test
public void givenUser_whenUseAnnotation_thenConverted(){
RelationalJMapper<User> relationalMapper = new RelationalJMapper<>(User.class);
User user = new User(1L, "john.doe@example.com");
UserDto1 result1 = relationalMapper.oneToMany(UserDto1.class, user);
UserDto2 result2 = relationalMapper.oneToMany(UserDto2.class, user);
assertEquals(user.getId(), result1.getId());
assertEquals(user.getEmail(), result1.getUsername());
assertEquals(user.getId(), result2.getId());
assertEquals(user.getEmail(), result2.getEmail());
}
9.3 使用 XML
user_jmapper2.xml
:
<jmapper>
<class name="com.example.jmapper.relational.User">
<attribute name="id">
<value name="id"/>
<classes>
<class name="com.example.jmapper.relational.UserDto1"/>
<class name="com.example.jmapper.relational.UserDto2"/>
</classes>
</attribute>
<attribute name="email">
<attributes>
<attribute name="username"/>
<attribute name="email"/>
</attributes>
<classes>
<class name="com.example.jmapper.relational.UserDto1"/>
<class name="com.example.jmapper.relational.UserDto2"/>
</classes>
</attribute>
</class>
</jmapper>
使用:
@Test
public void givenUser_whenUseXml_thenConverted(){
RelationalJMapper<User> relationalMapper = new RelationalJMapper<>(User.class, "user_jmapper2.xml");
User user = new User(1L, "john.doe@example.com");
UserDto1 result1 = relationalMapper.oneToMany(UserDto1.class, user);
UserDto2 result2 = relationalMapper.oneToMany(UserDto2.class, user);
assertEquals(user.getId(), result1.getId());
assertEquals(user.getEmail(), result1.getUsername());
assertEquals(user.getId(), result2.getId());
assertEquals(user.getEmail(), result2.getEmail());
}
📌 RelationalJMapper
的核心是 oneToMany()
方法,按目标类型分别映射。
10. 总结
JMapper 提供了三种灵活的映射配置方式:
- ✅ 注解方式:最简洁,适合大多数场景
- ✅ API 方式:动态配置,适合规则可变的系统
- ✅ XML 方式:集中管理,适合大型项目
- ✅ 自定义转换:支持复杂逻辑,扩展性强
- ✅ 关系映射:解决一源多目标的映射难题
性能上,JMapper 基于字节码生成,远快于反射型工具(如 Apache BeanUtils),在高并发场景下优势明显。
完整示例代码见:GitHub - JMapper Examples