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

📌 注意:

  • fromto 必须与字段注解中的源/目标字段一致
  • 方法必须是 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


原始标题:Guide to JMapper