Java对象复制之Cglib和mapstruct讲解

2022/9/17 14:17:24

本文主要是介绍Java对象复制之Cglib和mapstruct讲解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录
  • 1 Java对象复制
    • 1.1 Cglib复制
    • 1.2 mapstruct
      • 1.2.1 引入依赖
      • 1.2.2 需要转换的对象
      • 1.2.3 创建转换器
      • 1.2.4 原理讲解
      • 1.2.5 MapStruct进阶
    • 1.3 各个映射工具耗时对比

1 Java对象复制

1.1 Cglib复制

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

1.2 mapstruct

MapStruct是一个代码生成器,它基于约定优于配置,极大地简化了Java Bean类型之间映射的实现。特点如下:

  1. 基于注解
  2. 在编译期自动生成映射转换代码
  3. 类型安全、高性能、无依赖性、易于理解阅读

1.2.1 引入依赖

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'
}

1.2.2 需要转换的对象

@Data
public class Demo {
    private Integer id;
    private String name;
}

/**
 * 目标对象
 */
@Data
public class DemoDto {
    private Integer id;
    private String name;
}

1.2.3 创建转换器

只需要创建一个转换器接口类,并在类上添加 @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)
}

1.2.4 原理讲解

为什么声明一个接口就可以转换对象呢?我们看一下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.2.5 MapStruct进阶

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

1.3 各个映射工具耗时对比

附录:
image.png



这篇关于Java对象复制之Cglib和mapstruct讲解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程