介绍
最近换工作,趁这期间写了这个java
属性映射工具,前后用了大概两个整周吧。
写的时候参考了现在已有的一些映射工具如:mapstruct
、Dozer
、Orika
、BeanUtils
、PropertyUtils
、BeanCopier
等,从使用性能、功能、方便等角度写的这个属性映射工具,下面隆重推出今天的主角:moon-mapper
。
接下来从使用和原理两个角度介绍一下这个工具,已经发布到maven
仓库欢迎各种暴力测试,也希望大家使用过后多多提意见,让它更加强大。
一、使用介绍
Github: github.com/moonsky-all…
Gitee: gitee.com/moonsky-all…
1. 安装
<dependency>
<groupId>io.github.moonsky-all</groupId>
<artifactId>moon-mapper</artifactId>
<version>0.1</version>
</dependency>
复制代码
2. 基本使用
2.1. 注册映射器只需要在其中一个类上添加注解MapperFor
指定要映射的类即可定义两个类之间的映射关系;
2.2. 获取映射器BeanMapper mapper = Mapper2.get(Class, Class)
。
2.3. 使用映射器mapper.doForward(obj1, obj2)
public class UserEntity {
private String username;
private LocalDateTime birthday;
// 省略其他字段、getter、setter
}
// 添加这个注解就可以定义两个类之间具有映射关系
// 这里是 Class 数组,可以定义多个映射关系
@MapperFor({UserEntity.class})
public class UserDO {
// 获取映射器
// thisPrimary 是个简便方法,只能在有 MapperFor 注解的类使用
// 它获取的是本类与 MapperFor 中定义的第一个类之间的映射器
private static final BeanMapper MAPPER = Mapper2.thisPrimary();
private String username;
// 这个注解是用作格式化的,可用于格式化(解析)日期和数字
// 在非日期/数字与字符串之间的关系上会忽略掉
@MappingFormat("yyyy-MM-dd HH:mm:ss")
private String birthday;
// 省略其他字段、getter、setter
}
public class Main {
// Mapper2.get(Class, Class) 这是普通获取映射器的方式
private static final BeanMapper MAPPER = Mapper2.get(UserDO.class, UserEntity.class);
public static void main(String[] args) {
UserDO userDo = new UserDO();
// mapper 还有其他方法,可正向、也可反向映射
UserEntity entity = MAPPER.doForward(userDo, new UserEntity());
}
}
复制代码
3. 自动隐式类型转换
Mapper
支持常用数据类型之间的自动类型转换:
- 基本数据类型:
byte
、short
、int
、long
、float
、double
、char
、boolean
; - 基本数据类型包装类:
Byte
、Short
、Integer
、Long
、Float
、Double
、Character
、Boolean
; BigDecimal
、BigInteger
;- 日期类型:
java.util.Date
、util.Calendar
、sql.Date
、sql.Time
、sql.Timestamp
、java.time.XxxXxx
、joda.time.XxxXxx
等【建议joda-time 2.x+
】; - 枚举与字符串、数字之间的转换;
- 日期/数字 与字符串之间的格式化和解析字符串;
4. 基于setter
重载的自定义转换器
上面提到了默认的数据类型自动转换功能,但是有些情况下肯定是不能满足使用场景的, 所以另外提供了基于setter
重载的自定义转换器,由于java
语言特性,同名不同参数类型的方法可以重载, 当为字段提供另一个不同数据类型的setter
方法时,这里就相当于提供了一个指定类型的数据转换器,如:
public enum GenderEnum {
MALE("男"),
FEMALE("女");
public final String text;
GenderEnum(String text) { this.text = text; }
}
public class MemberEntity {
private GenderEnum gender;
// 省略其他字段、getter、setter
// 增加一个 String 类型的 gender setter 方法就是自定义转换器
public void setGender(String text) {
if ("男".equals(text)) {
this.gender = GenderEnum.MALE;
} else if ("女".equals(text)) {
this.gender = GenderEnum.FEMALE;
}
}
}
复制代码
可见自定义转换器还是比较简便的,而且充分利用了IDE
的代码提示功能以及java
语言特性,尽量减小用字符串的方式写代码也是当初设计moon-mapper
的一个初衷(会不会有人看到这句话感觉奇怪,为什么用字符串写代码?这个稍后会讲)
5. 支持Spring
环境
如果是在Spring
环境下(做Java
基本都在这环境吧[狗头]),如果应用的数据类是在Spring
扫描范围内可以使用自动注入,如:
@Service
public class BusinessService {
// Spring 自动注入
@Autowired
private BeanMapper<UserDO, UserEntity> userMapper;
}
复制代码
整体使用下来还是比较简单,添加注解MapperFor
注册,然后用Mapper2
获取映射器就可以使用了,接下来说明一下实现原理。
二、实现原理
Mapper2
是基于静态编译生成的映射器,这一点mapstruct
类似,生成的代码和手写get/set
一样,所以性能上是有保证的。
用@MapperFor
注册映射器后会生成两个Copier
分别代表正向和反向的属性复制器,和一个Mapper
映射器对前两个Copier
做了简单封装,在Spring
环境下都会添加注解@Component
,这样就可以实现自动注入了。
提示:
每次需要重新编译时记得Build -> Rebuild Project
(在IDEA中);
另外mvn clean compile
也会执行这一过程
属性复制是基于getter
和setter
方法进行的,所以参与复制的属性必须有public
修饰的setter/getter
方法,同时在处理映射关系时,会静态分析每个属性的数据类型,相同类型的同名属性可直接双向映射。如果是不同数据类型,优先使用基于setter
重载的自定义转换器,然后分析是否可使用一些默认转换,比如数字之间的转换、Date
和long
之间的转换、日期/数字格式化等,如果能转换就按转换的规则映射,否则就忽略同名不同类型和不同名属性的映射。
三、说明
- 支持
joda-time
日期库,但最好使用2.x
以上的版本,2.x
以下的版本虽然也做了一部分针对性处理,但不完善; - 目前总体分成了
mapper
和processing
两个包,其中processing
包里的所有内容最好都不要用,将来这个包一定会剥离出去; - 不同名属性之间的映射,目前没有单独提供这一功能,但可通过添加同名
setter
达到相同效果,目前这是第一版,以后会改进的; - 目前是第一版,仅实现了最基础的功能,希望大家能多提提意见和改进方案!!!