我们经常在代码里面使用到对象复制的相关操作,很多人应该都会使用BeanUtils这样的工具类,但是该工具类因为使用的是反射会导致效率低下 今天介绍mapstruct来进行对象复制 其原理就是通过getSet方法对对象的属性值记性复制
<!-- 引入pom坐标 -->
<!--对象复制-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.3.1.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.1.Final</version>
</dependency>
</dependencies>
// 两个对象
@Getter
@Setter
public class SourcePojo {
private String id;
private String name;
private int age;
private Order order;
public SourcePojo(){}
public SourcePojo(Builder builder){
this.id = builder.id;
this.name = builder.name;
this.age = builder.age;
this.order = builder.order;
}
public static class Builder{
private String id;
private String name;
private int age;
private Order order;
public Builder(){}
public Builder setName(String name){
this.name = name;
return this;
}
public Builder setId(String id){
this.id = id;
return this;
}
public Builder setAge(int age){
this.age = age;
return this;
}
public Builder setOrder(Order order){
this.order = order;
return this;
}
public SourcePojo createSourcePojo(){
return new SourcePojo(this);
}
}
@Getter
@Setter
@ToString
public class TargetPojo {
private String id;
private String name2;
private String age;
private Order order;
}
之后在接口里面定义对象转换的方式
/**
* 当前接口是为了映射source到target
*
* @author ygliu
* @createDate 2022/6/13
*/
@Mapper
public interface BeanConvert {
/**
* 将sourcePojo映射到TargetPojo
*
* @param sourcePojo 原数据
* @return 目标数据
*
* 因为target的属性name2要被source的name进行映射所以
* 这里通过Mapping进行指定映射
*/
@Mapping(target = "name2",source = "name")
TargetPojo sourceConvertTarget(SourcePojo sourcePojo);
可以定义多个.............
}
之后定义获取上述接口在编译期间生成的实现类获取
/**
* @author ygliu
* @createDate 2022/6/13
*/
@Configuration
public class BeanConvertConfig {
/**
* 获取相互转换的接口实现类
* 该实现类是mapstruct在编译期间启动生成接口的实现类
*
* @return 返回对象相互转换的实现
*/
@Bean
public BeanConvert getBeanConvert(){
return Mappers.getMapper(BeanConvert.class);
}
}
之后进行测试
@Autowired
private BeanConvert beanConvert;
@GetMapping("/test/convet")
public void testConvert() {
Order order = new Order("888","李四");
// 这里用的是静态建造者模式
SourcePojo sourcePojo = new SourcePojo.Builder()
.setAge(123)
.setId("666")
.setName("张三")
.setOrder(order)
.createSourcePojo();
TargetPojo targetPojo = beanConvert.sourceConvertTarget(sourcePojo);
System.out.println(targetPojo);
}
}
从运行结果可以看出来,需要注意的点是如果source里面有对象,那么mapstruct复制出来的就是指针指向而不是深拷贝,这里的原因是因为我们在映射接口里面对该字段的映射没有做其他操作,并且source和target的name都是一样的所以生成的set方法是直接将对象进行赋值也就是指针指向.
深拷贝引用数据类型的字段
如果想要对于target和source里面的引用类型进行深拷贝可以在对象互转的接口里面对字段进行手动映射,这样在实现类出来的时候就会new出来要映射的引用对象了
如果tagerPojo里面某个属性不是基本类型,而sourcePojo都是基本类型那么mapstruct可以实现映射吗,
看下面,想把SourcePojo里面的testInterceptor字段映射到TargetPojo里面的Order属性的testInterceptor
public class TargetPojo {
private String id;
private String name2;
private String age;
private Order order; // 里面有三个String类型的字段id,name,testInterceptor
}
public class SourcePojo {
private String id;
private String name;
private int age;
private String testInterceptor;
}
@Mapping(target = "name2",source = "name")
@Mapping(target = "order.testInterceptor",source = "testInterceptor")
TargetPojo sourceConvertTarget(SourcePojo sourcePojo);
测试结果: 成功!
TargetPojo(id=666, name2=张三, age=123, order=Order(id=null, name=null, testInterceptor=哈哈哈))
如果我们想对两个不同类型的字段进行映射有什么办法吗? 这里的类型是基本类型比如String -> String[]
// 源对象里面定义String类型的name
public class SourcePojo {
private String id;
private String name;
private int age;
private Order order;
private String testInterceptor;
}
// 目标对象定义String[]类型的name2
public class TargetPojo {
private String id;
private String[] name2;
private String age;
private String test;
private Order order;
}
/*注意expression里面的工具类一定要是全路径因为编译器创建实现类的
时候只是将外面的""去掉*/
@Mapping(target = "name2",expression =
"java(com.example.util.TestUtil.coverStringToArray(sourcePojo.getName()))")
TargetPojo sourceConvertTarget(SourcePojo sourcePojo);
// 编译后的实现类
@Override
public TargetPojo sourceConvertTarget(SourcePojo sourcePojo) {
if ( sourcePojo == null ) {
return null;
}
TargetPojo targetPojo = new TargetPojo();
targetPojo.setId( sourcePojo.getId());
targetPojo.setAge( String.valueOf( sourcePojo.getAge()));
targetPojo.setOrder( sourcePojo.getOrder());
targetPojo.setName2( com.example.util.TestUtil.coverStringToArray(sourcePojo.getName()) );
return targetPojo;
}
结果:
TargetPojo(id=666, name2=[张三, 李四], age=123, test=null,
order=Order(id=888, name=李四, testInterceptor=565464))
有时候我们会遇到转换相同的target不同的source但是source之间有很多相同的字段
我们可以使用@InheritConfiguration来指定source相同字段的映射方式
/**
* 转换对象基本类
*
* @param baseDto 入口基本类
* @return 返回对象
*/
@Mapping(source = "baseName",target = "name")
@Mapping(source = "baseName",target = "age")
WantToBean baseCoverTest(BaseDto baseDto);
/**
* 目标对象
*
* @param targetPojoOne 实现类1
* @return 目标对象
*/
@InheritConfiguration(name = "baseCoverTest")
@Mapping(source = "oneParam",target = "one")
WantToBean covetToOne(TargetPojoOne targetPojoOne);
但是这种方式有一个需要注意的地方,如果我们的转换对象里面存在自定义对象我们使用expression来指定的话会有有个问题继续看
这里我们在target里面添加了Order对象该对象里面有string数组属性names
/**
* 转换对象基本类
*
* @param baseDto 入口基本类
* @return 返回对象
*/
@Mapping(source = "baseName",target = "name")
@Mapping(source = "baseName",target = "age")
@Mapping(target = "order.names",expression =
"java(com.example.util.TestUtil.coverStringToArray(baseDto.getBaseName()))")
WantToBean baseCoverTest(BaseDto baseDto);
/**
* 目标对象
*
* @param targetPojoOne 实现类1
* @return 目标对象
*/
@InheritConfiguration(name = "baseCoverTest")
@Mapping(source = "oneParam",target = "one")
WantToBean covetToOne(TargetPojoOne targetPojoOne);
因为使用了expression这里面创建出来的实现类是硬编码所以在给对象赋值的时候传递的名字就是当前参数类的形参名这就导致了继承当前基本转换获取参数的形参名不一致
@Override
public WantToBean covetToOne(TargetPojoOne targetPojoOne) {
if ( targetPojoOne == null ) {
return null;
}
WantToBean wantToBean = new WantToBean();
wantToBean.setOne( targetPojoOne.getOneParam() );
wantToBean.setName( targetPojoOne.getBaseName() );
wantToBean.setAge( targetPojoOne.getBaseName() );
// 这里继承了baseCoverTest所以对order赋值
wantToBean.setOrder( targetPojoOneToOrder( targetPojoOne ) );
return wantToBean;
}
protected Order targetPojoOneToOrder(TargetPojoOne targetPojoOne) {
if ( targetPojoOne == null ) {
return null;
}
Order order = new Order();
/*因为使用了expression所以是硬编码 我们传递过来的形参名是targetPojoOne
但是expression写死的是baseDto所以这里会报错*/
order.setNames( com.example.util.TestUtil.coverStringToArray(baseDto.getBaseName()) );
return order;
}