BeanCopier
是Cglib包中的一个类,用于对象的复制。
注意:目标对象必须先实例化 而且对象必须要有setter方法
示例:
BeanCopier copier = BeanCopier.create(Source.class, Target.class, false); copier.copy(source, target, null);
第三个参数useConverter
,是否开启Convert
。默认BeanCopier
只会做同名,同类型属性的copier
,否则就会报错。如果类型需要转换比如Date转换成String则自定义Convert类实现Convert接口
重写convert
方法时,里面的三个参数:value
源对象属性,target
目标对象属性类,context
目标对象setter方法名
public class BeanCopyUtilDemo { public static void copy (Object source,Object target){ AccountConverter converter = new AccountConverter(); BeanCopier copier = BeanCopier.create(source.getClass(),target.getClass(),true); copier.copy(source,target,converter); } private static class AccountConverter implements Converter{ @Override // value 源对象属性,target 目标对象属性类,context 目标对象setter方法名 public Object convert(Object value,Class target,Object context){ Logger logger =LoggerFactory.getLogger(this.getClass()); try{ if(Objects.notNull(value) && StringUtils.isNoneBlank(value.toString())){ if(("BigDecimal").equals(target.getSimpleName())){ BigDecimal bd = new BigDecimal(String.valueOf("null".equals(value)?0:value)); return bd; } if(value instanceof BigDecimal){ BigDecimal bd = (BigDecimal) value; return bd.toPlainString(); } if(("Integer").equals(target.getSimpleName())){ Integer bd = new Integer(String.valueOf("null".equals(value)?0:value)); return bd; } if(value instanceof Integer){ Integer bd = (Integer) value; return bd.toPlainString(); } }else{ return null; } }catch (Exception e){ e.printStackTrace(); } return value; } } }
MapStruct
是一个代码生成器,它基于约定优于配置,极大地简化了Java Bean
类型之间映射的实现。特点如下:
pom.xml地址
<properties> <org.mapstruct.version>1.5.2.Final</org.mapstruct.version> </properties> <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <!-- depending on your project --> <target>1.8</target> <!-- depending on your project --> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> <!-- other annotation processors --> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
Gradle构建
dependencies { implementation 'org.mapstruct:mapstruct:1.4.2.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final' }
@Data public class Demo { private Integer id; private String name; } /** * 目标对象 */ @Data public class DemoDto { private Integer id; private String name; }
只需要创建一个转换器接口类,并在类上添加 @Mapper 注解即可(官方示例推荐以 xxxMapper 格式命名转换器名称)
import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @Mapper public interface DemoMapper { 使用Mappers工厂获取DemoMapper实现类 DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); 定义接口方法,参数为来源对象,返回值为目标对象 DemoDto toDemoDto(Demo demo); }
验证
public static void main(String[] args) { Demo demo = new Demo(); demo.setId(111); demo.setName("hello"); DemoDto demoDto = DemoMapper.INSTANCE.toDemoDto(demo); System.out.println("目标对象demoDto为:" + demoDto); 输出结果:目标对象demoDto为:DemoDto(id=111, name=hello) }
为什么声明一个接口就可以转换对象呢?我们看一下MapStruct在编译期间自动生成的实现类:
@Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2022-09-01T17:54:38+0800", comments = "version: 1.4.2.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-7.3.jar, environment: Java 1.8.0_231 (Oracle Corporation)" ) public class DemoMapperImpl implements DemoMapper { @Override public DemoDto toDemoDto(Demo demo) { if ( demo == null ) { return null; } DemoDto demoDto = new DemoDto(); demoDto.setId( demo.getId() ); demoDto.setName( demo.getName() ); return demoDto; } }
可以看到,MapStruct
帮我们将繁杂的代码自动生成了,而且实现类中用的都是最基本的get、set方法,易于阅读理解,转换速度非常快。
场景1:属性名称不同、(基本)类型不同
属性名称不同
: 在方法上加上 @Mapping
注解,用来映射属性属性基本类型不同
:基本类型和String等类型会自动转换关键字:@Mapping
注解
/** * 来源对象 */ @Data public class Demo { private Integer id; private String name; } /** * 目标对象 */ @Data public class DemoDto { private String id; private String fullname; } /** * 转换器 */ @Mapper public interface DemoMapper { DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); @Mapping(target = "fullname", source = "name") DemoDto toDemoDto(Demo demo); }
场景2:统一映射不同类型
下面例子中,time1、time2、time3都会被转换,具体说明看下面的注释:
/** * 来源对象 */ @Data public class Demo { private Integer id; private String name; /** * time1、time2名称相同,time3转为time33 * 这里的time1、time2、time33都是Date类型 */ private Date time1; private Date time2; private Date time3; } /** * 目标对象 */ @Data public class DemoDto { private String id; private String name; /** * 这里的time1、time2、time33都是String类型 */ private String time1; private String time2; private String time33; } /** * 转换器 */ @Mapper public interface DemoMapper { DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); @Mapping(target = "time33", source = "time3") DemoDto toDemoDto(Demo demo); MapStruct会将所有匹配到的: 源类型为Date、目标类型为String的属性, 按以下方法进行转换 static String date2String(Date date) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String strDate = simpleDateFormat.format(date); return strDate; } }
场景3:固定值、忽略某个属性、时间转字符串格式
一个例子演示三种用法,具体说明看注释,很容易理解:
关键字:ignore、constant、dateFormat
/** * 来源对象 */ @Data public class Demo { private Integer id; private String name; private Date time; } /** * 目标对象 */ @Data public class DemoDto { private String id; private String name; private String time; } /** * 转换器 */ @Mapper public interface DemoMapper { DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); id属性不赋值 @Mapping(target = "id", ignore = true) name属性固定赋值为"hello" @Mapping(target = "name", constant = "hello") time属性转为yyyy-MM-dd HH:mm:ss格式的字符串 @Mapping(target = "time", dateFormat = "yyyy-MM-dd HH:mm:ss") DemoDto toDemoDto(Demo demo); }
场景4:为某个属性指定转换方法
场景2中,我们是按照某个转换方法,统一将一种类型转换为另外一种类型;而下面这个例子,是为某个属性指定方法:
关键字:@Named注解、qualifiedByName
/** * 来源对象 */ @Data public class Demo { private Integer id; private String name; } /** * 目标对象 */ @Data public class DemoDto { private String id; private String name; } /** * 转换器 */ @Mapper public interface DemoMapper { DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); 为name属性指定@Named为convertName的方法进行转换 @Mapping(target = "name", qualifiedByName = "convertName") DemoDto toDemoDto(Demo demo); @Named("convertName") static String aaa(String name) { return "姓名为:" + name; } }
场景5:多个参数合并为一个对象
如果参数为多个的话,@Mapping
注解中的source就要指定是哪个参数了,用点分隔:
关键字:点(.)
/** * 来源对象 */ @Data public class Demo { private Integer id; private String name; } /** * 目标对象 */ @Data public class DemoDto { private String fullname; private String timestamp; } /** * 转换器 */ @Mapper public interface DemoMapper { DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); fullname属性赋值demo对象的name属性(注意这里.的用法) timestamp属性赋值为传入的time参数 @Mapping(target = "fullname", source = "demo.name") @Mapping(target = "timestamp", source = "time") DemoDto toDemoDto(Demo demo, String time); }
场景6:已有目标对象,将源对象属性覆盖到目标对象
覆盖目标对象属性时,一般null值不覆盖,所以需要在类上的@Mapper
注解中添加属性:
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE
代表null值不进行赋值。
关键字:@MappingTarget注解、nullValuePropertyMappingStrategy
/** * 来源对象 */ @Data public class Demo { private Integer id; private String name; } /** * 目标对象 */ @Data public class DemoDto { private String id; private String name; } /** * 转换器 */ @Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) public interface DemoMapper { DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); 将已有的目标对象当作一个参数传进来 DemoDto toDemoDto(Demo demo, @MappingTarget DemoDto dto); }
场景7:源对象两个属性合并为一个属性
这种情况可以使用@AfterMapping
注解
关键字:@AfterMapping注解、@MappingTarget注解
/** * 来源对象 */ @Data public class Demo { private Integer id; private String firstName; private String lastName; } /** * 目标对象 */ @Data public class DemoDto { private String id; private String name; } /** * 转换器 */ @Mapper public interface DemoMapper { DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class); DemoDto toDemoDto(Demo demo); 在转换完成后执行的方法,一般用到源对象两个属性合并为一个属性的场景 需要将源对象、目标对象(@MappingTarget)都作为参数传进来, @AfterMapping static void afterToDemoDto(Demo demo, @MappingTarget DemoDto demoDto) { String name = demo.getFirstName() + demo.getLastName(); demoDto.setName(name); } }
附录: