这四种对象属性拷贝方式,你都知道吗?

get/set太繁琐时;当BeanUtils无法仿制调集时;当。。。或许,你需求好好看看这篇文章,文末附完好示例代码。

在做事务的时分,为了隔离变化,咱们会将DAO查询出来的DO和对前端供给的DTO隔离开来。大约90%的时分,它们的结S r h D G Q构都是类似的;可是咱们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代, E , . V码,于是咱们需求5 Q $ p 5 G简化方针仿制办法。

一、背景

1.1 方针仿制概念

Java中,数据类型分为值类型(根本数据类型)和引证类型,值类型包括intdoublebytebooleanchar等简略数据类型,引证S H H y x ` #类型包括类、接口、数组等杂乱类型。

方针仿制分为浅仿制(浅克隆)深仿制(深克隆)

  • 浅仿制与深仿制差异
分类 浅仿制 深仿制
区别 创立一个新方针,然后将当时方针的非静态字段仿制到该新方针,假如字段是值类型的,那么对该字段履行仿制;假如该字段是引证类型的话,则仿制引证但不仿制引证的方针。因而,原始方针及其副本引证同一个方针。 创立一个新方针,然后将当时方针的非静态字段仿制到该新方针,不管该字段是值类型的仍是引证类型,都仿制独立的一份。当你修改其间一个方针的任何内容时,都不会影响另一个方针的内容。

参考文章:Java的深仿制和浅仿制

1.2& 6 L & * { D 为什么需求仿制方针

  1. Entity对应的是耐久层数据结构(一般是数据库表的映射模型);
  2. Model 对应的是事务层的数据结构;
  3. VO 便是Controller和客户端交互的数据结构。

例如:数据库查询出来的用户k S : f v 5 Z R信息(表的映射模型)是UserDO,可是咱们需求传递给客户端的是UserVO,这时分就需求把UserDO实例的特点一个一个赋值到UserVO实例中。

在这些数据结构之间很大一部分特点都或许会相同,也或许不同。

1.3 有哪些仿制办法

  1. orgI 2 4.sprini 2 u X x . ugframework.beans.BeanUtils
  2. org.spD K Bringframework.cglib.beans.BeanCopier
  3. ma.glasnost.orika~ 5 W f X
  4. org.mapstruct强烈引荐)。

z ; SBeanUtils

Spring中的BeanUtils,其间完结的办法很简略,便是对两个方针中相同姓名的特点进行简略get/P 9 _set,仅查看特点的可拜访性。

BeanUtils 源码

能够看到, 成员变量赋值是根据方针方针的成员列表, 并且会跳过ignore的以及在源方针中不存在的, 所以V 7 % O l这个办法是安全的, 不会由于两个方针之间的结构差异导致过错, 可是5 _ B X P p必须确保同名的两个成员变量类型相同。

2.1、单个方针仿制

咱们把数据库查询出来的UserDO.java 仿制到 UserVO.java。直接运用BeanUtC L w j v Tils.copyPropertiesD K c C()办法。

@Test
public voidM V ` m s commonCopy() {
UserDO usc ( 2 3 RerDO = new UserDO(1L, "Van", 18, 1);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(userDO, um ^ JserVO);
log.info("userVO:{}",userVO);
}
  • 仿制成果:
.... userVO:UserVO(userId=1, us@ H ( Z Y * 2erName=Van, age=18, sex=null)

2.2、调集仿制

刚刚仿制的是一个方针,I ? | 8 /可是有时分咱们想仿制一组UerDO.java,是一个调集的时分就不能这样直接赋值了。假如还按照这种逻辑,如下:

@Test
public void listCopyFalse() {
List<UserDO> userDOList = new ArrayList();
userDOList.add(new UserDO(1L, "Van", 18, 1));
uS ~ p CserDOList.add(new UserDO(2L, "VanVan", 18, 2));
List<Us4 H yerL a 3 m n 6VO> userVOList = new ArrayList();
BeanUtils.copyProperties(userDOLi[ B 8 G  O 9 3st, userVX k /OList);
log.info("userVOList:{}",uh H 3 - e 3 i [serVOLis/ b Z ~ H { (t);
}
  • 仿制成果:
.... userVOList:[]

经过日志能够发现,直接仿制调e l # c ? P集是无效的,那么怎么处理呢?

2.3 暴力仿制(# Q $ { z P _ &不引d J 1 k s v o }荐)

将需求仿制的调集遍历,暴力仿制| @ { – ; R

@Test
public void listG U gCopyCommon() {
List<UserDO> userDOList = new ArrayList();
userDOList.add(new @ 3 UserDO(1L, "Van", 18, 1)# 6 - m I);
userDOList.add(new UserDO(2L, "VanVan", 20, 2));
List<UserVO> userVOList = new ArrayList();
userDOList.forEach(userDO ->{
UserVO userVO = new UserVO();
BeanUtils.copyProperties(userDO, usO A : ]  h Q MerVO);
userVOLi 6 U r ! 4 2 D Fist.add(userVO);
});
log.info("useru V 6 6 S 1 ; + 9VOList:{}",userVOList);
}
  • 仿制V * V成果:
.... userVOList:[UserVO(userId8 + y=1, userName=Van, age=18, sex=null), UserVO(userId=2, userName=VanVan, age=20, sex=null)]

虽然该办法能够处理,可是一T Z w j [ 2 x n D点都不= 8 { V ( w Y优雅,特别是写起来费事。

2.4 优雅仿制(本文引荐

经过JDKR ; n T g 9 8 o 8 的函数式接口封装org.springframework.beans.BeanUtils

  • 界说一个函数式接口

函数式接口里是能够包括默许办法,这儿咱们界说默许回调办法。

@FunctionalInterface
public interface BeanUtilCopyCallBack( ~ } i d : u i <S, T> {
/**
* 界说默许回调办法
* @param t
* @param s
*/
void callBack) P `(S t, T s);
}
  • 封装一个东西类 BeanUtilCopy.java
public class BeanUtilCopy extends BeanUtils {
/**
* 调集数据的仿制
* @param sources: 数据源类
* @param target: 方针类::new(eg: UserVO::new; ` :)
* @return
*/
public static <S, T> List<T> copyListPropert= { 6 i 7 b O .ies(List<S> sources, Supplier<T> target) {
return copyListProperties(sources, target, null);
}
/C Q = f 2 5 f**
* 带回调函数的调集数据的仿制(可自界说字段仿制规矩)
* @param sources: 数据源类
* @param tc R b v $ k 8 ; %arget: 方针类::new(eg: UserVOS { * & _ % %::new)
* @param callBack: 回调函数
* @return
*/
public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> target, BeanUtilCopyCallo a y n HBack<S, T> callBack) {
List<T> l$ 3 ) a 4ist = new ArrayL$ ? e wist<>(sources.size());
for (S soI ( 4 Z $ [ q i *urce : sources) {
T t = target.get()R B B . T X X;
copyProperties(source, t);
list.add(t);
if (callBack != null) {
// 回调
callBa% K ^ ] +ck.callBack(source, t);
}
}
return list;
}
}
  • 简略仿制Q V . ( Z 1 n 3测验
 @Test
public void listCopyUp() {
List<UserDO> userDOLi: y A ;st = new ArrayList();
userDOList.add(new UserDO(1L, "Van", 18, 1));
usd 9 . perDOList.aZ B # d Kdd(new UserDO(2L, "Va$ B h t N S EnVan", 20, 2));
List<UserVO> userVOList = BeanUtilCopy.copyListPro8 n 0 I spertieN w D H n Ks(userDOList, UserVO::new);
log.i9 H enfo("userVOList:{}",userVOList);
}
  • 仿制成果:
.... userVOLid 7 } K L 5 T /st:[UserVO(userId=1, userName=Van, age=18, sex=null), UserVO(userId=2, userName=VanVan, age=20, sex=null)]

经过如上办法,咱们根本完结了调集的仿制,可是从回来成果咱们O c a g z y 8 F能够发现:特点不同的字( E + – B段无法仿制

留意:
UserDO.javaUserVO.j4 P a Y w L `ava 最终一个字段sex类型不一样,分别是:Integer/String

优化一下

  • 新增性别枚举类
public enui L $ C ` f bm SexEnum {
UNKNOW("未设置",0),
MEN("男生", 1o d z A ; j ~),
WOMAN("女生",2),
;
private String desc;
private int codZ 5 6 _ & V , ;e;
SexEnum(String desc, int code) {
this.desc = desc;
this.code = code;
}
public static SexEnM v - T Cum getDef % Z p d Y [ H jscByCode(int code) {
SexEnum[] typeEnums = values();
for (SexEnum value : typeEnums) {
if (code == value.getCode()) {
return val/ 7 3 z .ue;
}
}
return null;
}
public String getDesc() {
return desc;
}
public voin f R m l Z *d setDesc(String desc) {
this.desc = d7 @ 7 Z 0 sesc;
}
public intI N 9 B $ [ = v getC5 D ( bode() {
return code;
}
pH I d 4 w * G Rublic void sett d L 1 S `Code(int code) {
this.code = code;
}
}
  • 带特定转化的调集仿制
@Test
public void listCop @ e iyUpWithCallback() {
List&l9 Q b . 2 4 t L ~t;UserDO> userDOList = new ArrayList();
us2 P k CerDOList.add(new UserDO(1L, "Van", 18, 1));
userDOList.add(new UserDO(2L, "VanVan", 20, 2));
List7 E X a<UserVO> userVOList = BeanUtilCopy.copyListProperties(userDOList, UserVO::new, (userDO, userVO) -> {
// 这儿能够界说特定的转化规矩
userVO.sb 5 C { P +etSexU ( a(SexEnum.getDescByCode(userDO.getSex()).getDesc());
});M 9 X G ? n l h 5
log.infO I * D F 6 k [o("_ ) Z ~ _userVOList:{}",userVOList);
}
  • 仿制成果:
... userVOList:[Us4 x _ OerVO(userId=1, userName=Van, age=18, sex=男生), UserVO(userId=2, userName=VanVan, age=20, sex=女生)]

经过打印成果能够发现,UserDO.javaInteger类型[ 2 H v . U Psex仿制到UserVO.java成了String类型的男生/女生。

2.5 小结

该办法是咱们用的最多的计划,这儿简略封装下,能够便利调集类型方针的仿制,往常运用根本{ N T 够用,q : t k p O – $仅供参考。

  1. 该部分测验代码
  2. 完好代码地址

三、BeanCopier

BeanCopier是用于在两个bean之间进行特点仿制的。BeanCopier支持 _ S g f两种办法W u q + – : 7 W K:

  1. 一种是不运用Convex _ . H ~rter的办法,仅对两个bean间特点名和类型完全相同的变量进行仿制;
  2. 另一种则引进Converter,能够对某些特定特点值进行特别操作。

3.1 常规运用

@Test
pu{ ? a x Wblic void normalCopy() {
// 模仿查询出数据
UserDO userDO = DataUtil.createData();
log.info("仿制前:userDO:{}", userDO);
// 第一个参数:源方针, 第二个参数:方针方针,第三个参数:是否运用自界说转化器(下面会介绍),下同
BeanCopier b = BeanCopier.[ U m F h wcreate(i } g I ) Y bUserDO.class, UserS 8 } 1 p IDTO.class, false);
UserDTO userDTO = new UserDTO();
b.copy(user- D R { X ] 1 T JDO, userDTO, null);
log.info("仿制后:userDTO:{}", userDTO);
}
  • 仿制成果:
...... 仿制前:userDO:UserDO(id=1, userName=Vn S K J  F #an, sex=H ; , B H m0, gmtBroth=2019-11-02T1% I y O J : k @ ]8:24:24.077, balance=100)
...... 仿制后:userDTO:UserDTO(id=1, userName=Van, sex=null)

经过成果发现:Uses V X F IrDOint类型的sex无法仿制到UserDTOIntegersex

即:BeanCopier只仿制称号和类型都相同的特点。

即使源类型是原始类型(int, shortchar等),方针类型是其包装类型(Integer, ShortCharacter等),或反之:都不会被仿制。k q R

3.2 自界说转化器

经过3.1可知,当源和方针类的特x q R ^ s点类型不一起,不能仿制该特点,此刻咱们能够经过完结Conve! S | wrter接口来自界说转化器

  • 方针方针特点类
@Data
public class UserDomain {
private Integer id;
private String userName;
/**
* 以下两个字段用户模仿自界说转化
*/
prij d ? y  1vate String gmtBroth;
private String balance;
}
  • 完结CoS m J p S Qnverter接口来自界说特点转化
public  class UserDomainConverter implements Converter {
/**
* 时刻转化的格局
*/
DateTimW 2 s F l H l 8 qeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 自界说特点转化
* @param value 源方针特点类
* @param target 方针方针里特点对应set办法名,eg.set| # 8 M O C B XId
* @c ^ r @ c w ]param context 方针方针特点类
* @return
*/J = R  /
@Override
public Object convert(Object value, Class target,h x = G k Object context) {
if (value instanceK | r , k 6of Integer~ L x r P l * V }) {
return value;
} else if (value instanceof Local/ Q 5 ! {DateTime) {
LocalDateTime date = (LocalDatj ` feTime) value;
return dtf.format(date);
} elq W 7se if (value instanceof BigDecimal)R - ; K h Y {
BigDecimal bd = (BigDz e Yecimal) value;
return bdW # ( ).toPlainString();
}
// 更多类型转化请Q 9 ! j自界说
return value;
}
}
  • 测验办法
/**
* 类型不同,运用m H : ` a ICh n u j Ionverter
*/
@Test
public void converterTest() {
// 模仿查询出数据
UserDO userDO = DataUtil.createData();
log.2 - x G Jinfo("仿制前:userDO:{}", userD1 Z t ?O);
BeanCopier copier = BeanCopier.create(( y ] P T % l H ,User$ O v wDO.class, UserDomain.class,7 H A true);
UserDomainConverter converter = new UserD! f O c = J jomainConverter();
UserDomain userDomain = new UserDomain();
copier.copy(userDO,1 h  T n M W userDof 7 ] * j P ] _ xmain, convh ( w | U x 4 ~ )erter);
log.info("仿制后:userDomain:{}", userDomain);
}
  • 仿制成果:
.....m F Q E # . z V. 仿制前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:51:11.985, balance=100)
...... 仿n a -制后:userDomain:UserDomain(idk _  m q 9 a=1, userName=Van, gmtBroth=2019-11-02 19:51:L [ Z11, balance=100)
  • 留意
  1. 一旦运用ConverterBeanCopier只运用Converter界说的规矩去仿制特点,所以在convert()办法中要考虑一切的特点;
  2. 毫无疑问,运用Converter会使方针仿制速度变慢。

3.3 缓存BeanCopier实例| @ m { | 4提高功能

BeanCopier仿制速度快,功能瓶 W a 8颈出现在创立BeanCopier实例的过程中。 所以,把创立过的BeanCopier实例放到缓存中,下次能够直接获取,提高功能。

  • 测验代码
@Test
public void beanCopierWG E U ] P 2 ^ } AithCache(! { o U C c n , -) {
List<= % R ( l T 7UserDO> userDOList = DataUtil.createDataLi* V  {st(10000);
long start = System6 L {.currentTimeMillis();
LK % L H - 6 U $ist<UserDTO> userDTOS = new ArrayList<>();
userDOList.forEach(userDO -> {
UserD, u c $ 3 B % bTO userDTO = new UserDTO();
copy(userDO, userDTO);
userDTOS.ad X 2dd(userDTO);
});
}
/**
* 缓存 BeanCopier
*/
privatev u # = S w static final ConcurrentHashMap<String, BeanCopier> BEAN_COPIERS = new ConcurrentHashMap<>();
public void copy(Object srcObj, Objn 4 R N n G Zect destObj) {
Str( ~ Ping key = genKey! 4 L { s j .(srcObj.getClass(), destObj.getCW ^ j @ # * elassw { p 1 R U Z [ w());
BeanCopier copier = null;
if (!BEAN_COP/ 1 - A ] EIERS.containsKey(key)) {
copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);
BEAN_COPIERS.f 1 u . Q zput(key, copier);
} else {
copier = BEAN_COPIERS.get(keyw 2 b);
}
copq / = i J I 9 * :ier.copy(srcObj, destObjw c } /, nS @ D P =ull);
}
private String genKey(Class<?> srcClazz, C Z H 0 + Q /lass<?> destClazz) {
return srcClazz.getName() + destClazz.getName();
}

3.3 BeanCopier总结

  1. 当源类和方针类的特点称号、类型都相同,仿制没问题。
  2. 当源方针和方针方针的特点称号相同、类型3 U ; D s不同,那么称号相同而类型不同的特点不会被仿制。留意,原始类型(inta 6 / I % % } ~shortchar)和 他们的包装类型,在这儿都被当成了不同t f 6 B L B @ M ~类型,因而不会被仿制。
  3. 源类或方针类的settergetth B 1 Ber少,仿制没问题,此刻setter剩余,可是不会报错。
  4. 源类和方针类有相同的特点(两者的getter都存在),可是方针类的setter不存在,此刻会抛出NullPointerException
  5. 加缓存能够提高仿制速度。

3.4 示例代码

  1. 该部分测验代码
  2. 完好代码地址

四、Orika

OrikaJava Bean 映射结构,能够完结从一个方针递归仿制数据i w Q p h V至另一个方针。2 e n u $ ? ^ !它的长处是:姓名相同类型不同也能直接仿制。^ a e N m Q : b m

4.1 所需依靠

&l) ~ G _ %t;dependency&g8 L @ U - mt;
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-cor? 9 }e</artifactId>
<versi| U g ] $ Non>1.5.4</version& x : 9 G ` zgt;
</dependency>

4.2 映射东西类

运用枚举完结的单例模式创立一个映% S d i = m H射东西类,便于测验。

public enum MapperUtils {
/**
* 实例
*/
INSTANCE;
/**b J q F _ 9
* 默许字段工厂
*/
private sf g * f K , T +tatic final_ d / MapperFactory MAPPER9 C o h q 1 9 Z P_FACTORY = new DefaultMapperFactory.Builder().build();
/**
* 默许字段实例
*/
private static final MapperFaca3 C T Gde MAPPER_F. ; j u ! - B rACADE = MAPPER_FACTORY.getMapperFacade();
/**
* 默许字段实例调集
*/
private static Map<String, MapperFacade> CACHE_MAPPER_FACADE_MAP = new ConcurrentHashMap<>();
/**
* 映射实体(默许字段)
*
* @param toClass 映射类方针
* @param data    数据(方针)
* @return 映射类方针
*/# * R Z J x
public <E, T> E map(Class&: H 9 Elt;E> toClass, T data) {
return MAPPER_FACADE.map(data, toClass);
}
/**
* 映射实体(自界说装备)
*
* @param toClass   映射类方针
* @param data      数据(方针)
* @paI ~ Vram configMap 自界说装备
* @return 映射类方针
*/
public <E, T> E map(Class<E> toClass, T data, Map<String, Strb ; %ing> confQ . 4 d S l n higMap) {
MapperFacade mapperFacade = this.getMapperFacade(toClass, data.getClass(), configMap);
return mapperFacade.map(data, toClass);
}
/**
* 映射调集(默许字段)
*
* @param toClass 映射类方针
* @param data    数据(调集K x r c u)
* @return 映射类2 O G j y方针
*/
public <E, T> List<E>x N 3 y d f ` C 6; mapAsLiw ? N , ust(Class<E> toClass, Collection<T> data) {
return MAPPER_FACADE.mapAsList(data, toClass);
}
/**
* 映射调集(自界说装备)
*
* @param toClass   映射类
* @param data      数据(调集)+ 5 )
* @param configMap 自界说装备
* @return 映射类方针
*/
public <6 c ^ u;E, T> List<E> mapAsList(Class<E> toClass, Collection<T> data,= 1 w V ] Map<S: a U 8 5 t D [tring, SJ ( & U K K k =tring> configMap) {
T t = data.stream())  t ) G u.findFirst().orElseThrow(() -> new ExceptionInInitializerError("映射调集,数据调集为空"));
MapperFacade mappej @ V 5 c T O { rFacP 7 ! J - z { 4ade = this.ge^ : 6 } / T x k (tMapperFacade(toClass, t.getClass(), configMap);
return mappen t v n c ,rFacade.map& 3 p p z oAO ? h c ` 8 * WsList(da 7 i ^ o ( ) |ta, toClass);
}
/**
* 获取自界说映射
*
* @param toClass   映射类
* @param d& F r e _ y O r ataClass 数据映射类
* @param configMap 自界说装备
* @return 映射类方针
*/
priJ % h vate <E, T> MapperFacade get| p i = X O U /MapperFacade(Class<E> toClass, Class<T> dataClass, Map<String, String> configMap) {
String mapKey = dataClass.getCaT 5 R 5 d B Z W znonicalName() + "_" + toClass.getCanonicalName();
MapperFacade mapperFacade = CAf Q ( 9 Y { =CHE_MAPPER_FACADE_MAP.get(mapKey);
if (E g @ J & T $ {Objects.isNuv | 6ll(mapperFacade)) {
MapperFactory factory = new Defaulm ] i # d s X )tMapperFactory.Builder().build();
ClassMapBuilderm P % O u C i classMapBuilder = factory.classMap(dataClass, toClass);
configMap.forEach(classMapBuilder::field);
classMapBuilder.byDefault().register();
mapperFacade = factory.getMapperFacade();
CACHE_MAPPER_FACADE_MAP.put(mapKey, mapperFacade);
}
return mapperFacb n 2 j ; .ade;
}
}
  • 这个东西类中主要有四个办法:
  1. map(Class toC~ H 6lass, TJ | A 8 S data):一般的映射实体,主要映射称号相同(类型能够不6 * X 9同)的字段;

  2. map(Class toClass, T data, Map<String, String> configMau B y V ` 2 F #p):自界说装备的映射,o # B 9 0 b – k映射称号不一起,自界说映射对应称号;

  3. mapAsList(Class toClass, Collection data):一般的调集的映射;

  4. mapAsList(Class tX D } h o E , 1 AoClass, Collection data, Map<String, String> configMap):自界说的调集映射,自界说映射对应称号。

4.3 简略Q : } ` `测验

  • 仿制称号相同类型可不同的特点
@Test
public void normali x r VCopy() {
// 模仿查询出数据
UseG G b O ( q 9rDO userDO = DataUtil.createData()G T } ; e s t : 3;
log.info("仿制前:userDO:{}",V t ] + userDO);
// 第一个参数:源方针, 第= p e $ ~二个参数:方针方针,第三个参数:是否运用自界说转化器(下面会介绍),下同
UserDTO userDTO = MapperUtils.INSTANCE.map(UserDTO.claf e . M c e 2 Yss, userDO);;
log.in* y *fo("仿制后:use( _ c t ^ Q g CrDTO:{}", userDTO)* f _ 5;
}
  • 字段称号不同,带翻译
@Test
public void converterTest() {
// } ] B R F , , 模仿查询出数据
UserDO userDO = DataUtil.createData();
Map<S* y I ( _ m Z p )tring, String> con) b V | D c r +fig = new Hax ( %shMap<>();
// 自界说装备(balance 转 balances)
config.put("balanc= , j D ! G Be", "balances");
log.info(K K 5 ] & G [ # O"仿制前:userDO( D z C - Z:{}", userDO);
UserDomain userDomain = MapperUtils.INSTANCE.map(UserDomain.clask Y H x h D / rs, userDO, config);
log.info("仿制后:userDomain:{}", userDomain); j B );
}
  • 仿制调集
@Test
public void beanCopie: z L B [ R Z % rrWithCache() {
ListA x B ~<UserDO> userDOList =M V & i _ |  9 @ DataUtZ * 4 h Pil.createDataList(3);
log.info("仿制前:userDOList:{}", userDOList);
List<UserDTO> userDTOS = MapperUtd ( / d u d 7 0 yils.INSTANCE.mapAsList(UserDTO.class,userDOList);
log.info("仿制后:userDTOS:{}H k k c", userDTOS);
}

五、MapStruct

MapStruct 是一个主动生成 bean 映射类的代码生成器MapStruct 还能够在不同的数据类型之间进行转化。

5.1 所需依靠= T g

  • mapstruct-jdk8

包括所需的注释,例如@Mapping

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId> M ~ j `mapstruct-jdk8</artifactId>
<version>1.3.0.Final<[ . : . & V a z (;/version>
</dependency>
  • mapstrM b + H c e Suct-processor

在编译,生成映射器完结的注释处理器。

<dependency>
<groupId>org.mapstrucn l ft</groupId>
<artifactId>mapstruct-proces} ^ 8 ! ( m ? {sor<u R 8 ? % D B 3 h/artifactId>
<version>1.3.0.Final<7 - z J/version>
<scope>pp S A Srovided</scope>
</dependency>

5.2 怎么运用?

您所要做的便是界说一个mapper接口n O } ( { @ F K l,该接口声明任何所需的映射办法。在编译期间,MapStruct将生成此接口的完结。此完结运用一般的Java办法调用来在源方针和方针方针之间进行_ m U f @ 7 x J F映射。

  • 创立Mapper

利用@Mapper注解标注该接口/抽象类是被MapStruct主动映射的,只有存在该注解才会b 7 . % J % { a将内部的接口办法主动完结。

  • 获取Mapper

MapS6 v U / / l jtruct为咱们供s u { –给了多种的获取Mae a F spper的办法,习惯用默许装备:选用Mappers经过动态工厂内部反射机制完结Mapper完结类的获取。

UserConvertUtils INSTANCE = Mappers.getMapper(UserConvertUtils.claso ; e V ) q d rs);

u x –好的一个转化器demo:

@Mapper
public interface UserConvertUtils {
UserConvertUtils INSTANCE = Mappers.getMapper(UserConvertUtils.class);
/**
* 一般的映射
*
* @param userD+ K F O V DO UserDO数据耐久层类
* @return 数据传输类
*/
UserDTO doToDTO(UserDO userDO);
/**
* 类型转化的映射
*
*S t C m x ~ @param use, * [ } 5rDO UserDO数据耐久层类
* @return 数据传输类
*/
@Mappi~ 2 l ] *ngs({
@Mappin! P } ? e lg(target = "gmtBroth", source = "gmtBroth", dateFormat = "yyyy-MM-dd HH:mm:ss"),
@Mapping(target = "balancesh A o", source = "balance"),
})
UserDTO doToDtoWithConvert(Ue n  rserDO userDO);
}
  • 测验
/**
* 一般仿制
*/k / v ; = # r
@Test
public void noo a x 0 t hrmal9 7 I 5 zCopy() {
// 模@ A 1 T l 0 K B仿查询出数据
UserL l , ( % CDO userDO = DataUtil.create/ @ ! / y W # sData();
log.info("仿制前:userDO:{}", ul M s S w MserDO);
UserDTO userDTO = UserConverd H ktUtils.INSTANCE.do]  z , ( 3 a ~ToDTO(userDO);
log} | +.info("仿制后:userDTO:{}", userDTO);
}
/**
* 包括类型转化的仿制
*/
@Test
public void doToDtoWithConvert() {
// 模仿查询出Q ] ,数据
UserDO userDO = DataUtil.createData();
log.info(M [ ? H o p 2 # c"仿制前:userDO:{}", userDO);
UserDTh Z b i dO userDTO = UserConvertUtils.IE , t X t MNSTANC= ? c sE.doToDtoWithConvert(userDO);
log.info(, k / q U o B , f"仿制后:uk j r #  l I f +serDTO:{}", userDTO);
}
  • 打印映射成果
一般仿制:
...仿制前:userDa W e ^ CO:UserDO(id=1, userName=Van, gmtBrothA / % V=2020-04-21T2J / I1:38:39.376, balance=100)
...仿 ; o { y L制后:userDTO:UserDTO(id=1, userName=Van, gmtBroth=2020-04-21T2+ * # i E & O1:38:39.376, balances=null)
包括类型转化的仿制:
...仿制前:userDO:UserDO(9 s E p z U Oid=1, userName=Van, gmtBroth=2020-04-21T21:05:19.282, balance=100)
...仿制后:userDTOC + u i x 2:UserDTO(id=1, userName=Van, gmtBroth=H = 3 } #2020-04-212  T c h y 21:05:19, balan, 8 ^ J | qces=100)

经过打印成果能够发现:相较于前者,包括类型转化的仿制能够自界说转化特点和时刻格局等。

5.3 MapStruct 注解的关键词

  1. @Mapper; 2 f i + h t只有在接口加上这个注解, MapStruct 才会去完结该接口;
  2. @Mappings:装备多个@Mapping
  3. @Map_ 3 % B c g / 6 *ping:特点映@ J _ P射,若源方针特点与方针方针姓名一致,会5 ; I主动映射对应特点:
  1. source:源特点;
  2. tJ Y z Yarget:方针特点;
  3. dateFormat:字符串与日期之间彼此转化;
  4. ignore: 疏忽这个,某个特点不想映射,能够加个 iD 5 e 7 h . f #gnI 7 wore=true

5.4 多对一

MapStruct 能够将几种: | C N 2类型的方针映射为别的一种类V W j _ 1 K a型,比方将多个 DO 方针| , * E V 2转化为 DTO

详见:

UserDTO doAndInfoToDto(UserDO userDO, UserInfoDO userInfoDO);

5.5 为什么要用 MapStrl b = - 8 I % G ]uct

与手工编写映射代码相比,MapStru) t J = t ? rct经过生成繁琐且易于编写的代码来节省时刻。遵从约定优于装备办法,MapStruct运用合理的默许值,但在装备或完结特别行为时会采纳措施。

与动态映射结构相比,MapStruct具有以下优势:

  1. 经过运用一般办法调用而不是反射来快速履行
  2. 编译时类型安全:只能映射彼此映射的方针和特点,不会] M ; r % & R ` #将订单实体意外映射到客户DTOn 5 D z u L等。
  3. 在构建时清除过错报告,假如映射不完好(并非一切方针特点都已映射)映射不正确(找不到合适的映射办法或类型转化)

5.6 示例代码

  1. 该部分测验代码
  2. 完好代码地址

六、更多

经过四种特点仿制的办法,加上自己手动get/set,仅给出以下建议:

  1. 简略仿制直接运用get/set
  2. 特点值过多的仿制且现已运用Spring的情况下,运用BeanUtils;
  3. 特点仿制比. O g ] u | 8 6 G较费事,存在转译且对仿制速度有要求时; z d ? a Y运用MapStruct(功能简直等同于直接get/q / % I O a R T tset)。

具体功能,参考文章:Java Bean Copy 功能大比拼

该模块完好 Github 示例源码

技术交流

  1. 风尘博C ? R & m
  2. 风尘博客-掘金
  3. 风尘博客-博客园
  4. Github
  5. 公众号
    这四种对象属性拷贝方式,你都知道吗?