优雅的对象转换解决方案,为何更推荐 MapStruct 呢?

第一次看到 MapStruct 的时候, 我我的很是的开心。由于其跟我心里里面的想法不谋而合。java

1 MapStruct 是什么?

1.1 JavaBean 的困扰

对于代码中 JavaBean之间的转换, 一直是困扰我好久的事情。在开发的时候我看到业务代码之间有不少的 JavaBean 之间的相互转化, 很是的影响观感, 却又不得不存在。我后来想的一个办法就是经过反射, 或者本身写不少的转换器。整理了一份Java面试宝典完整版PDF已整理成文档git

第一种经过反射的方法确实比较方便, 可是如今不管是 BeanUtils, BeanCopier 等在使用反射的时候都会影响到性能。虽然咱们能够进行反射信息的缓存来提升性能。可是像这种的话, 须要类型和名称都同样才会进行映射, 有不少时候, 因为不一样的团队之间使用的名词不同, 仍是须要不少的手动 set/get 等功能。github

第二种的话就是会很浪费时间, 并且在添加新的字段的时候也要进行方法的修改。不过, 因为不须要进行反射, 其性能是很高的。面试

1.2 MapStruct 带来的改变

MapSturct 是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。缓存

抓一下重点:安全

  1. 注解处理器app

  2. 能够生成 JavaBean 之间那的映射代码ide

  3. 类型安全, 高性能, 无依赖性

从字面的理解, 咱们能够知道, 该工具能够帮咱们实现 JavaBean 之间的转换, 经过注解的方式。工具

同时, 做为一个工具类,相比于手写, 其应该具备便捷, 不容易出错的特色。性能

2 MapStruct 入门

入门很简单。我是基于 Maven 来进行项目 jar 包管理的。

2.1 引入依赖

<properties>
        <org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
</properties>

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

2.2 建立entity和dto对象

该类是从 github 某个订单系统里面拿下来的部分。

@Data
public class Order {

    /**
     *订单id
     */
    private Long id;

    /**
     * 订单编号
     */
    private String orderSn;

    /**
     * 收货人姓名/号码
     */
    private String receiverKeyword;

    /**
     * 订单状态:0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单
     */
    private Integer status;

    /**
     * 订单类型:0->正常订单;1->秒杀订单
     */
    private Integer orderType;

    /**
     * 订单来源:0->PC订单;1->app订单
     */
    private Integer sourceType;
}

对应的查询参数

@Data
public class OrderQueryParam {
    /**
     * 订单编号
     */
    private String orderSn;

    /**
     * 收货人姓名/号码
     */
    private String receiverKeyword;

    /**
     * 订单状态:0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单
     */
    private Integer status;

    /**
     * 订单类型:0->正常订单;1->秒杀订单
     */
    private Integer orderType;

    /**
     * 订单来源:0->PC订单;1->app订单
     */
    private Integer sourceType;

}

2.3 写 Mapper

Mapper 即映射器, 通常来讲就是写 xxxMapper 接口。固然, 不必定是以 Mapper 结尾的。只是官方是这么写的。在本入门例子中,对应的接口以下

import com.homejim.mapstruct.dto.OrderQueryParam;
import com.homejim.mapstruct.entity.Order;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper
public interface OrderMapper {

    OrderQueryParam entity2queryParam(Order order);

}

简单的映射(字段和类型都匹配), 只有一个要求, 在接口上写 @Mapper 注解便可。而后方法上, 入参对应要被转化的对象, 返回值对应转化后的对象, 方法名称可任意。

2.4 测试

写一个测试类测试一下。

@Test
public void entity2queryParam() {
    Order order = new Order();
    order.setId(12345L);
    order.setOrderSn("orderSn");
    order.setOrderType(0);
    order.setReceiverKeyword("keyword");
    order.setSourceType(1);
    order.setStatus(2);

    OrderMapper mapper = Mappers.getMapper(OrderMapper.class);
    OrderQueryParam orderQueryParam = mapper.entity2queryParam(order);
    assertEquals(orderQueryParam.getOrderSn(), order.getOrderSn());
    assertEquals(orderQueryParam.getOrderType(), order.getOrderType());
    assertEquals(orderQueryParam.getReceiverKeyword(), order.getReceiverKeyword());
    assertEquals(orderQueryParam.getSourceType(), order.getSourceType());
    assertEquals(orderQueryParam.getStatus(), order.getStatus());

}

测试经过, 没有任何的问题。

3 MapStruct 分析

上面中, 我写了3个步骤来实现了从 Order 到 OrderQueryParam 的转换。

那么, 做为一个注解处理器, 经过MapStruct 生成的代码具备怎么样的优点呢?

3.1 高性能

这是相对反射来讲的, 反射须要去读取字节码的内容, 花销会比较大。而经过 MapStruct 来生成的代码, 其相似于人手写。速度上能够获得保证。

前面例子中生成的代码能够在编译后看到。在 target/generated-sources/annotations 里能够看到。

image

生成的代码

对应的代码

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2019-08-02T00:29:49+0800",
    comments = "version: 1.3.0.Final, compiler: javac, environment: Java 11.0.2 (Oracle Corporation)"
)
public class OrderMapperImpl implements OrderMapper {

    @Override
    public OrderQueryParam entity2queryParam(Order order) {
        if ( order == null ) {
            return null;
        }

        OrderQueryParam orderQueryParam = new OrderQueryParam();

        orderQueryParam.setOrderSn( order.getOrderSn() );
        orderQueryParam.setReceiverKeyword( order.getReceiverKeyword() );
        orderQueryParam.setStatus( order.getStatus() );
        orderQueryParam.setOrderType( order.getOrderType() );
        orderQueryParam.setSourceType( order.getSourceType() );

        return orderQueryParam;
    }
}

能够看到其生成了一个实现类, 而代码也相似于咱们手写, 通俗易懂。

3.2 易于 debug

在咱们生成的代码中, 咱们能够轻易的进行 debug。

image

易于 DEBUG

在使用反射的时候, 若是出现了问题, 不少时候是很难找到是什么缘由的。

3.3 使用相对简单

若是是彻底映射的, 使用起来确定没有反射简单。用相似 BeanUtils 这些工具一条语句就搞定了。可是,若是须要进行特殊的匹配(特殊类型转换, 多对一转换等), 其相对来讲也是比较简单的。整理了一份Java面试宝典完整版PDF已整理成文档

基本上, 使用的时候, 咱们只须要声明一个接口, 接口下写对应的方法, 就可使用了。固然, 若是有特殊状况, 是须要额外处理的。

3.4 代码独立

生成的代码是对立的, 没有运行时的依赖。

相关文章
相关标签/搜索