Bean 自动映射工具

Posted mry6

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Bean 自动映射工具相关的知识,希望对你有一定的参考价值。

Bean 自动映射工具

平时做项目的时候,经常需要做PO、VO、DTO之间的转换。简单的对象转换,使用BeanUtils基本上是够了,但是复杂的转换,如果使用它的话又得写一堆Getter、Setter方法了。今天给大家推荐一款对象自动映射工具MapStruct。

关于BeanUtils

我平时经常使用Hutool中的BeanUtil类来实现对象转换,用多了之后就发现有些缺点:

1》对象属性映射使用反射来实现,性能比较低;
2》对于不同名称或不同类型的属性无法转换,还得单独写Getter、Setter方法;
3》对于嵌套的子对象也需要转换的情况,也得自行处理;
4》集合对象转换时,得使用循环,一个个拷贝。

对于这些不足,MapStruct都能解决,是一款功能强大的对象映射工具。

MapStruct简介

MapStruct是一款基于Java注解的对象属性映射工具,使用的时候我们只要在接口中定义好对象属性映射规则,它就能自动生成映射实现类,不使用反射,性能优秀,能实现各种复杂映射。

IDEA添加MapStruct插件

作为一款非常流行的对象映射工具,MapStruct还提供了专门的IDEA插件,我们在使用之前可以先安装好插件。

项目集成

在SpingBoot中集成MapStruct非常简单,仅续添加如下两个依赖即可,这里使用的是1.4.2.Final版本。

<!--MapStruct相关依赖-->
<dependency>
	<groupId>org.mapstruct</groupId>
	<artifactId>mapstruct</artifactId>
	<version>1.4.2.Final</version>
</dependency>
<dependency>
	<groupId>org.mapstruct</groupId>
	<artifactId>mapstruct-processor</artifactId>
	<version>1.4.2.Final</version>
	<scope>compile</scope>
</dependency>

添加链接描述项目gitee地址:https://gitee.com/mry6/springboot-mapstruct

基本映射

1、首先我们准备好要使用的会员PO对象Member;

/**
 * 购物会员实体类
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class Member 

    private Long id;
    private String username;
    private String password;
    private String nickname;
    private Date birthday;
    private String phone;
    private String icon;
    private Integer gender;


2、然后再准备好会员的DTO对象MemberDto,我们需要将Member对象转换为MemberDto对象;

/**
 *  购物会员Dto
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberDto 

    private Long id;
    private String username;
    private String password;
    private String nickname;
    //与PO类型不同的属性
    private String birthday;
    //与PO名称不同的属性
    private String phoneNumber;
    private String icon;
    private Integer gender;


3、然后创建一个映射接口MemberMapper,实现同名同类型属性、不同名称属性、不同类型属性的映射;

/**
 * 会员对象映射
 */
@Mapper
public interface MemberMapper 

    MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);

    @Mapping(source = "phone",target = "phoneNumber")
    @Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
    MemberDto toDto(Member member);


4、接下来在Controller中创建测试接口,直接通过接口中的INSTANCE实例调用转换方法toDto;

/**
 *  MapStruct对象转换测试Controller
 */
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController 

    @ApiOperation(value = "基本映射")
    @GetMapping("/baseMapping")
    public CommonResult baseTest() 
        List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
        MemberDto memberDto = MemberMapper.INSTANCE.toDto(memberList.get(0));
        return CommonResult.success(memberDto);
    


5、项目运行后在Swagger中测试接口,发现PO所有属性已经成功转换到DTO中去了,Swagger访问地址:http://localhost:8088/swagger-ui.html

6、其实MapStruct的实现原理很简单,就是根据我们在Mapper接口中使用的@Mapper和@Mapping等注解,在运行时生成接口的实现类,我们可以打开项目的target目录看下;

7、下面是MapStruct为MemberMapper生成好的对象映射代码,可以和手写Getter、Setter说再见了!

public class MemberMapperImpl implements MemberMapper 
    public MemberMapperImpl() 
    

    public MemberDto toDto(Member member) 
        if (member == null) 
            return null;
         else 
            MemberDto memberDto = new MemberDto();
            memberDto.setPhoneNumber(member.getPhone());
            if (member.getBirthday() != null) 
                memberDto.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(member.getBirthday()));
            

            memberDto.setId(member.getId());
            memberDto.setUsername(member.getUsername());
            memberDto.setPassword(member.getPassword());
            memberDto.setNickname(member.getNickname());
            memberDto.setIcon(member.getIcon());
            memberDto.setGender(member.getGender());
            return memberDto;
        
    

集合映射

MapStruct也提供了集合映射的功能,可以直接将一个PO列表转换为一个DTO列表,再也不用一个个对象转换了!

1、在MemberMapper接口中添加toDtoList方法用于列表转换;

@Mapping(source = "phone",target = "phoneNumber")
@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
List<MemberDto> toDtoList(List<Member> list);

2、在Controller中创建测试接口,直接通过Mapper接口中的INSTANCE实例调用转换方法toDtoList;

@ApiOperation(value = "集合映射")
@GetMapping("/collectionMapping")
public CommonResult collectionMapping() 
    List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
    List<MemberDto> memberDtoList = MemberMapper.INSTANCE.toDtoList(memberList);
    return CommonResult.success(memberDtoList);

3、在Swagger中调用接口测试下,PO列表已经转换为DTO列表了。

子对象映射

MapStruct对于对象中包含子对象也需要转换的情况也是有所支持的。

1、例如我们有一个订单PO对象Order,嵌套有Member和Product对象;

/**
 * 订单实体类
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class Order 

    private Long id;
    private String orderSn;
    private Date createTime;
    private String receiverAddress;
    private Member member;
    private List<Product> productList;

/**
 *  商品
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class Product 
    private Long id;
    private String productSn;
    private String name;
    private String subTitle;
    private String brandName;
    private BigDecimal price;
    private Integer count;
    private Date createTime;

2、我们需要转换为OrderDto对象,OrderDto中包含MemberDto和ProductDto两个子对象同样需要转换;

/**
 * 订单dto
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class OrderDto 
    private Long id;
    private String orderSn;
    private Date createTime;
    private String receiverAddress;
    //子对象映射Dto
    private MemberDto memberDto;
    //子对象数组映射Dto
    private List<ProductDto> productDtoList;

/**
 * 商品dto
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class ProductDto 
    //使用常量
    private Long id;
    //使用表达式生成属性
    private String productSn;
    private String name;
    private String subTitle;
    private String brandName;
    private BigDecimal price;
    //使用默认值
    private Integer count;
    private Date createTime;

3、我们只需要创建一个Mapper接口,然后通过使用uses将子对象的转换Mapper注入进来,然后通过@Mapping设置好属性映射规则即可;

/**
 * 订单对象映射
 */
@Mapper(uses = MemberMapper.class,ProductMapper.class)
public interface OrderMapper 

    OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);

    @Mapping(source = "member",target = "memberDto")
    @Mapping(source = "productList",target = "productDtoList")
    OrderDto toDto(Order order);


4、接下来在Controller中创建测试接口,直接通过Mapper中的INSTANCE实例调用转换方法toDto;

@ApiOperation(value = "子对象映射")
@GetMapping("/subMapping")
public CommonResult subMapping() 
    List<Order> orderList = getOrderList();
    OrderDto orderDto = OrderMapper.INSTANCE.toDto(orderList.get(0));
    return CommonResult.success(orderDto);


private List<Order> getOrderList() 
    List<Order> orderList = LocalJsonUtil.getListFromJson("json/orders.json", Order.class);
    List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
    List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
    for (int i = 0; i < orderList.size(); i++) 
        Order order = orderList.get(i);
        order.setMember(memberList.get(i));
        order.setProductList(productList);
    
    return orderList;

5、在Swagger中调用接口测试下,可以发现子对象属性已经被转换了。

合并映射

MapStruct也支持把多个对象属性映射到一个对象中去。

1、这里把Member和Order的部分属性映射到MemberOrderDto中去;

/**
 * 会员商品信息组合Dto
 * Created by macro on 2021/10/21.
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberOrderDto extends MemberDto
    private String orderSn;
    private String receiverAddress;

2、然后在Mapper中添加toMemberOrderDto方法,这里需要注意的是由于参数中具有两个属性,需要通过参数名称.属性的名称来指定source来防止冲突(这两个参数中都有id属性);

@Mapping(source = "member.phone",target = "phoneNumber")
@Mapping(source = "member.birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
@Mapping(source = "member.id",target = "id")
@Mapping(source = "order.orderSn", target = "orderSn")
@Mapping(source = "order.receiverAddress", target = "receiverAddress")
MemberOrderDto toMemberOrderDto(Member member, Order order);

3、接下来在Controller中创建测试接口,直接通过Mapper中的INSTANCE实例调用转换方法toMemberOrderDto;

@ApiOperation(value = "组合映射")
@GetMapping("/compositeMapping")
public CommonResult compositeMapping() 
    List<Order> orderList = LocalJsonUtil.getListFromJson("json/orders.json", Order.class);
    List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
    Member member = memberList.get(0);
    Order order = orderList.get(0);
    MemberOrderDto memberOrderDto = MemberMapper.INSTANCE.toMemberOrderDto(member,order);
    return CommonResult.success(memberOrderDto);

4、在Swagger中调用接口测试下,可以发现Member和Order中的属性已经被映射到MemberOrderDto中去了。

使用依赖注入

上面我们都是通过Mapper接口中的INSTANCE实例来调用方法的,在Spring中我们也是可以使用依赖注入的。

1、想要使用依赖注入,我们只要将@Mapper注解的componentModel参数设置为spring即可,这样在生成接口实现类时,MapperStruct会为其添加@Component注解;

/**
 * 会员对象映射(依赖注入)
*/
@Mapper(componentModel = "spring")
public interface MemberSpringMapper 
    @Mapping(source = "phone",target = "phoneNumber")
    @Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
    MemberDto toDto(Member member);

2、接下来在Controller中使用@Autowired注解注入即可使用;

@Autowired
private MemberSpringMapper memberSpringMapper;

@ApiOperation(value = "使用依赖注入")
@GetMapping("/springMapping")
public CommonResult springMapping() 
    List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
    MemberDto memberDto = memberSpringMapper.toDto(memberList.get(0));
    return CommonResult.success(memberDto);

3、在Swagger中调用接口测试下,可以发现与之前一样可以正常使用。

使用常量、默认值和表达式

使用MapStruct映射属性时,我们可以设置属性为常量或者默认值,也可以通过Java中的方法编写表达式来自动生成属性。

1、下面这个商品类Product对象;

/**
 *  商品
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class Product 
    private Long id;
    private String productSn;
    private String name;
    private String subTitle;
    private String brandName;
    private BigDecimal price;
    private Integer count;
    private Date createTime;

2、我们想把Product转换为ProductDto对象,id属性设置为常量,count设置默认值为1,productSn设置为UUID生成;

/**
 * 商品dto
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class ProductDto 
    //使用常量
    private Long id;
    //使用表达式生成属性
    private String productSn;
    private String name;
    private String subTitle;
    private String brandName;
    private BigDecimal price;
    //使用默认值
    private Integer count;
    private Date createTime;

3、创建ProductMapper接口,通过@Mapping注解中的constant、defaultValue、expression设置好映射规则;

/**
 *  商品对象映射
 */
@Mapper(imports = UUID.class)
public interface ProductMapper 
    ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);

    @Mapping(target = "id",constant = "-1L")
    @Mapping(source = "count",target = "count",defaultValue = "1")
    @Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
    ProductDto toDto(Product product);

4、接下来在Controller中创建测试接口,直接通过接口中的INSTANCE实例调用转换方法toDto;

@ApiOperation(value = "使用常量、默认值和表达式")
@GetMapping("/defaultMapping")
public CommonResult defaultMapping() 
    List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
    Product product = productList.get(0);
    product.setId(100L);
    product.setCount(null);
    ProductDto productDto = ProductMapper.INSTANCE.toDto(product);
    return CommonResult.success(productDto);

5、在Swagger中调用接口测试下,对象已经成功转换。

在映射前后进行自定义处理

MapStruct也支持在映射前后做一些自定义操作,类似AOP中的切面。

1、由于此时我们需要创建自定义处理方法,创建一个抽象类ProductRoundMapper,通过@BeforeMapping注解自定义映射前操作,通过@AfterMapping注解自定义映射后操作;

/**
 * 商品对象映射(自定义处理)
 */
@Mapper(imports = UUID.class)
public abstract class ProductRoundMapper 
    public static ProductRoundMapper INSTANCE = Mappers.getMapper(ProductRoundMapper.class);

    @Mapping(target = "id",constant = "-1L")
    @Mapping(source = "count",target = "count",defaultValue = "1")
    @Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
    public abstract ProductDto toDto(Product product);

    @BeforeMapping
    public void beforeMapping(Product product)
        //映射前当price<0时设置为0
        if(product.getPrice().compareTo(BigDecimal.ZERO)<0)
            product.setPrice(BigDecimal.Bean 自动映射工具

使用IDEA工具整合mybatis时使用@Resource和@Autowired自动注解bean时会显示红色问题的解决办法

基于reflectasm打造自己的通用bean工具

干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大!

干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大!!

干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大!!