推荐GET/SET 提效工具
饭哥2021-10-25

本文简要介绍了 GenerateAllSetter / Lombok / Mapstruct 三种法器,各自应用场景稍有不同,供大家参考选择。

背景

日常开发过程中,往往绕不开 DTO / DO / VO 等基础对象定义,或对象属性转换等相关编码。针对对象属性 Getter / Setter 场景,本文简要介绍了 GenerateAllSetter / Lombok / Mapstruct 三种法器,各自应用场景稍有不同,供大家参考选择。

GenerateAllSetter

简介

GenerateAllSetter 是什么?

GenerateAllSetter 是一款 IDEA 插件,可为对象属性批量生成 Setter 代码。

应用场景

什么时候用?

对象属性赋值,希望自动生成对象所有属性的 Setter 方法。

基本用法

怎么用?

IDEA 安装插件 GenerateAllSetter

IDEA-Preferences-Plugins,搜索 GenerateAllSetter ,一键安装插件。插件安装后,若未生效请重启 IDEA 。

一键生成对象 Setter 方法

GenerateAllSetter 安装完成后,选中目标对象,单击左边灯泡或使用快捷键 MacOS( Option + 回车)/ Windows( Alt + 回车),即可一键生成对象的 setXxx 方法。

例如,点击 “Generate all setter with default value”,一键生成对象所有 Setter 方法(预设默认值)。

Lombok

简介

Lombok 是什么?

Lombok 是一款 Java 开发插件,可在编译期自动生成对象的基础方法。例如, POJO 类的构造器、 Getter/Setter 、equals 和 toString 等方法,借助 Lombok 只需添加相应注解即可自动生成,无需手动定义。

应用场景

什么时候用?

对象基础方法定义,希望自动生成 Getter / Setter / toString 等基础方法,让类定义更简洁。

使用方法

怎么用?

引入 Lombok 依赖

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.16.18</version>
</dependency>

核心注解@Data

作用于类时,为类的所有属性生成 Getter/Setter 方法,并生成类的 toString / equals / canEquals / hashCode 方法。Lombok 自动生成 Getter / Setter 方法时,不会覆盖显式定义的 Getter / Setter 方法。

@Setter:作用于类时,为类的所有属性生成 Setter 方法;作用于类的某个属性时,为该属性生成 Setter 方法。

@Getter:作用于类时,为类的所有属性生成 Getter 方法;作用于类的某个属性时,为该属性生成 Getter 方法。

@AllArgsConstructor:作用于类时,生成该类的全参构造函数。

@NoArgsConstructor:作用于类时,生成该类的无参构造函数。

@ToString:作用于类时,生成对应的 toString 方法。

@EqualsAndHashCode:作用于类时,重写 equals 和 hashcode 方法。

@Builder:通过 Builder 链式创建新对象,不需要逐行添加 Setter 方法,可简化代码行数,提升编码体验。

@NotNull:作用于方法入参,如果对应入参传了 null 值,将抛出空指针异常。

@Synchronized:作用于方法,可锁定指定对象;如果不指定,则默认创建一个对象锁定。

@Accessors(chain = true):使用链式设置属性,Setter 方法返回 this 对象。

@RequiredArgsConstructor:在类上添加 @RequiredArgsConstructor(staticName = "of") 时,生成一个静态方法。

@FieldDefaults:设置属性使用范围,如 private / public 等,也可以设置属性是否被 final 修饰。

@Cleanup: 关闭流对象、连接点等。

示例 1 :

@Data 注解,自动生成对象的 Getter、Setter、equals、hashcode 和 toString 方法。

编译后生成代码如下:

示例 2 :

@Getter / @Setter / @AllArgsConstructor / @NoArgsConstructor / @Builder 注解,分别生成对象的 Getter、Setter、全参构造器、无参构造器和 Builder 方法。

编译后生成代码如下:

更多功能,可参考官网:https://projectlombok.org

Mapstruct

简介

Mapstruct 是什么?

Mapstruct 是一款 Java 属性映射工具,可实现源对象和目标对象之间的属性映射。通过 Mapstruct 定义 Mapper 接口,Mapstruct 将在编译期生成该 Mapper 接口的实现类,该实现类内部封装了对象属性转换逻辑。值得注意的是,Spring 和 Apache 提供了 BeanUtils 工具,同样可实现对象属性转换,但由于底层基于反射实现,需在运行期进行属性转换,效率相对较低,因此应尽量避免使用 BeanUtils 工具。

应用场景

什么时候用?

对象间属性映射,希望自动映射关联属性,或自定义不同属性的映射关系。

使用方法

怎么用?

引入 Mapstruct 依赖

<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>${org.mapstruct.version}</version>
</dependency>

对象定义

举例,PosOrder 对象转换为 WorkOrder 对象,两个对象属性不完全对等。

PosOrder(订单):

@Data
public class PosOrder implements Serializable {
    private static final long serialVersionUID = 690962385718485646L;
    /**
     * 记录生成时间
     */
    private Date gmtCreate;
    /**
     * 订单ID
     */
    private Long id;
    /**
     * 买家信息
     */
    private User user;
    
    // ...
}

WorkOrder(工单):

@Data
public class WorkOrder implements Serializable {
    private static final long serialVersionUID = 890962385718485649L;
    /**
     * 记录生成时间
     */
    private Date gmtCreate;
    /**
     * 工单ID
     */
    private String workNo; 
    /**
     * 买家信息
     */
    private Customer customer;
    /**
     * 删除标记
     */
    private String isDeleted;
    /**
     * 记录序列ID
     */
    private String sequenceId;
}

WorkOrderMapper 转换器定义

定义接口 WorkOrderMapper,并添加类注解 @Mapper ,MapStruct 会自动生成对应实现类。

@Mapper 的 componentModel 属性,用于指定实现类的类型。

  1. default:通过 Mappers.getMapper(Class) 方式获取实例对象;
  2. spring:接口实现类自动添加注解 @Component,可通过 @Autowired 方式注入。

@Mapping:属性映射,若源对象属性与目标对象属性名字一致,将自动映射同名属性;此外,支持自定义属性映射关系。

  1. source:源属性
  2. target:目标属性
  1. dateFormat:String 和 Date 日期相互转换
  2. ignore: 忽略这个字段

WorkOrderMapper 代码如下:

@Mapper
public interface WorkOrderMapper {
    /**
     * Mapper实例
     */
    WorkOrderMapper INSTANCE = Mappers.getMapper(WorkOrderMapper.class);

    /**
     * PosOrder 转换为 WorkOrder
     * 说明:
     * - PosOrder.id 映射为 WorkOrder.workNo
     * - PosOrder.user.userName 映射为 WorkOrder.customer.name
     * - WorkOrder.isDeleted 默认为常量"n"
     * - WorkOrder.sequenceId 通过 getSequenceId() 方法动态生成
     */
    @Mapping(target = "workNo", source = "id" )
    @Mapping(target = "customer.name", source = "user.userName")
    @Mapping(target = "isDeleted", constant = "n")
    @Mapping(target = "sequenceId",  expression="java(getSequenceId())")
    WorkOrder transFrom(PosOrder posOrder);
    
    /**
     * WorkOrder 转换为 PosOrder
     */
    @InheritInverseConfiguration(name="transTo")
    PosOrder transTo(WorkOrder workOrder);
    
    /**
     * 映射器可添加自定义方法,如获取序列ID
     */
    default String getSequenceId(){
        String uuid = UUID.randomUUID().toString();
        return uuid;
    }
}

编译后生成的 Mapper 实现类如下:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2021-09-27T21:55:09+0800",
    comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_151 (Oracle Corporation)"
)
public class WorkOrderMapperImpl implements WorkOrderMapper {
    @Override
    public WorkOrder transFrom(PosOrder posOrder) {
        if (posOrder == null) {
            return null;
        }
        WorkOrder workOrder = new WorkOrder();
        workOrder.setCustomer(userToCustomer(posOrder.getUser()));
        if (posOrder.getId() != null) {
            // Mapping 映射属性生成
            workOrder.setWorkNo(String.valueOf(posOrder.getId()));
        }
        // 同名属性自动映射
        workOrder.setGmtCreate(posOrder.getGmtCreate());
        // 设置默认值
        workOrder.setIsDeleted("n");
        // 自定义方法
        workOrder.setSequenceId(getSequenceId());
        return workOrder;
    }

    @Override
    public PosOrder transTo(WorkOrder workOrder) {
        if (workOrder == null) {
            return null;
        }
        PosOrder posOrder = new PosOrder();
        posOrder.setUser(customerToUser(workOrder.getCustomer()));
        if (workOrder.getWorkNo() != null) {
            posOrder.setId(Long.parseLong(workOrder.getWorkNo()));
        }
        posOrder.setGmtCreate(workOrder.getGmtCreate());
        return posOrder;
    }
    
    /**
     * 内部对象映射
     */
    protected Customer userToCustomer(User user) {
        if ( user == null ) {
            return null;
        }
        Customer customer = new Customer();
        customer.setName(user.getUserName());
        // 同名属性自动映射
        customer.setId(user.getId()); 
        return customer;
    }
    
  /**
     * 内部对象映射
     */
    protected User customerToUser(Customer customer) {
        if ( customer == null ) {
            return null;
        }
        User user = new User();
        user.setUserName( customer.getName() );
        user.setId( customer.getId() );
        return user;
    }

对于更复杂的属性转换,Mapstruct 还支持 List / Map / 枚举映射 / 时间 等类型转换。需要提醒的是,编译后最好检查 Mapstruct 自动生成的代码,以避免可能的异常情况,如类型自动转换可能丢失精度等。

借助 WorkOrderMapper 转换器实现对象转换

定义 WorkOrderMapper 后,即可直接使用 Mapper 实例将 PosOrder 转换为 WorkOrder 对象。

WorkOrder workOrder =  WorkOrderMapper.INSTANCE.transfer(posOrder);

在上面例子中, 我们通过 Mappers.getMapper(xxx.class) 方式获取对应 Mapper 。接口本身定义了 INSTANCE 字段,用于获取 Mapper 实例。此外,Mapstruct 支持 Spring 依赖注入方式,通过 @Mapper(componentModel = “spring”) 即可将当前 Mapper 注册进 Spring 容器,后续在其它类直接注入即可。

总结

本文简要介绍了三种对象属性编码提效工具。

  1. GenerateAllSetter 用于自动生成对象属性的 Setter 方法;
  2. Lombok 用于自动生成类的 Getter / Setter / toString 等基础方法;
  1. Mapstruct 用于实现两个对象之间的属性转换。

三种工具都为开发者提供了较好的便捷性,并一定程度上提升了代码可读性。值得注意的是,很多工具可在编译期自动生成代码,但使用者应该熟悉其基本原理,以避免可能的异常情况,出现问题能及时感知与排查。