• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

关于java:干掉-BeanUtils试试这款-Bean-自动映射工具真心强大

java 搞代码 4年前 (2022-01-27) 44次浏览 已收录 0个评论
文章目录[隐藏]

平时做我的项目的时候,常常须要做PO、VO、DTO之间的转换。简略的对象转换,应用BeanUtils基本上是够了,然而简单的转换,如果应用它的话又得写一堆Getter、Setter办法了。明天给大家举荐一款对象主动映射工具MapStruct,性能真心弱小!

SpringBoot实战电商我的项目mall(50k+star)地址:https://github.com/macrozheng/mall

对于BeanUtils

平时我常常应用Hutool中的BeanUtil类来实现对象转换,用多了之后就发现有些毛病:

  • 对象属性映射应用反射来实现,性能比拟低;
  • 对于不同名称或不同类型的属性无奈转换,还得独自写Getter、Setter办法;
  • 对于嵌套的子对象也须要转换的状况,也得自行处理;
  • 汇合对象转换时,得应用循环,一个个拷贝。

对于这些有余,MapStruct都能解决,不愧为一款功能强大的对象映射工具!

MapStruct简介

MapStruct是一款基于Java注解的对象属性映射工具,在Github上曾经有4.5K+Star。应用的时候咱们只有在接口中定义好对象属性映射规定,它就能主动生成映射实现类,不应用反射,性能优良,能实现各种简单映射。

IDEA插件反对

作为一款十分风行的对象映射工具,MapStruct还提供了专门的IDEA插件,咱们在应用之前能够先装置好插件。

我的项目集成

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

<code class="xml"><dependency>
    <!--MapStruct相干依赖-->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${mapstruct.version}</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>${mapstruct.version}</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

根本应用

集成完MapStruct之后,咱们来体验下它的性能吧,看看它有何神奇之处!

根本映射

咱们先来个疾速入门,体验一下MapStruct的基本功能,并聊聊它的实现原理。

  • 首先咱们筹备好要应用的会员PO对象Member
<code class="java">/**
 * 购物会员
 * Created by macro on 2021/10/12.
 */
@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;
}
  • 而后再筹备好会员的DTO对象MemberDto,咱们须要将Member对象转换为MemberDto对象;
<code class="java">/**
 * 购物会员Dto
 * Created by macro on 2021/10/12.
 */
@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;
}
  • 而后创立一个映射接口MemberMapper,实现同名同类型属性、不同名称属性、不同类型属性的映射;
<code class="java">/**
 * 会员对象映射
 * Created by macro on 2021/10/21.
 */
@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);
}
  • 接下来在Controller中创立测试接口,间接通过接口中的INSTANCE实例调用转换方法toDto
<code class="java">/**
 * MapStruct对象转换测试Controller
 * Created by macro on 2021/10/21.
 */
@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);
    }
}
  • 运行我的项目后在Swagger中测试接口,发现PO所有属性曾经胜利转换到DTO中去了,Swagger拜访地址:http://localhost:8088/swagger-ui
  • 其实MapStruct的实现原理很简略,就是依据咱们在Mapper接口中应用的@Mapper@Mapping等注解,在运行时生成接口的实现类,咱们能够关上我的项目的target目录看下;
  • 上面是MapStruct为MemberMapper生成好的对象映射代码,能够和手写Getter、Setter说再见了!
<code class="java">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列表,再也不必一个个对象转换了!

  • MemberMapper接口中增加toDtoList办法用于列表转换;
<code class="java">/**
 * 会员对象映射
 * Created by macro on 2021/10/21.
 */
@Mapper
public interface MemberMapper {
    MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);

    @Mapping(source = "phone",target = "phoneNumber")
    @Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
    List<MemberDto> toDtoList(List<Member> list);
}
  • 在Controller中创立测试接口,间接通过Mapper接口中的INSTANCE实例调用转换方法toDtoList
<code class="java">/**
 * MapStruct对象转换测试Controller
 * Created by macro on 2021/10/21.
 */
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {

    @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);
    }
}
  • 在Swagger中调用接口测试下,PO列表曾经转换为DTO列表了。

子对象映射

MapStruct对于对象中蕴含子对象也须要转换的状况也是有所反对的。

  • 例如咱们有一个订单PO对象Order,嵌套有MemberProduct对象;
<code class="java">/**
 * 订单
 * Created by macro on 2021/10/12.
 */
@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;
}
  • 咱们须要转换为OrderDto对象,OrderDto中蕴含MemberDtoProductDto两个子对象同样须要转换;
<code class="java">/**
 * 订单Dto
 * Created by macro on 2021/10/12.
 */
@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;
}
  • 咱们只须要创立一个Mapper接口,而后通过应用uses将子对象的转换Mapper注入进来,而后通过@Mapping设置好属性映射规定即可;
<code class="java">/**
 * 订单对象映射
 * Created by macro on 2021/10/21.
 */
@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);
}
  • 接下来在Controller中创立测试接口,间接通过Mapper中的INSTANCE实例调用转换方法toDto
<code class="java">/**
 * MapStruct对象转换测试Controller
 * Created by macro on 2021/10/21.
 */
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
    
    @ApiOperation(value = "子对象映射")
    @GetMapping("/subMapping")
    public CommonResult subMapping() {
        List<Order> orderList = getOrderList();
        OrderDto orderDto = OrderMapper.INSTANCE.toDto(orderList.get(0));
        return CommonResult.success(orderDto);
    }
}
  • 在Swagger中调用接口测试下,能够发现子对象属性曾经被转换了。

合并映射

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

  • 例如这里把MemberOrder的局部属性映射到MemberOrderDto中去;
<code class="java">/**
 * 会员商品信息组合Dto
 * Created by macro on 2021/10/21.
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberOrderDto extends MemberDto{
    private String orderSn;
    private String receiverAddress;
}
  • 而后在Mapper中增加toMemberOrderDto办法,这里须要留神的是因为参数中具备两个属性,须要通过参数名称.属性的名称来指定source来避免抵触(这两个参数中都有id属性);
<code class="java">/**
 * 会员对象映射
 * Created by macro on 2021/10/21.
 */
@Mapper
public interface MemberMapper {
    MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);

    @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);
}
  • 接下来在Controller中创立测试接口,间接通过Mapper中的INSTANCE实例调用转换方法toMemberOrderDto
<code class="java">/**
 * MapStruct对象转换测试Controller
 * Created by macro on 2021/10/21.
 */
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
    
    @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);
    }
}
  • 在Swagger中调用接口测试下,能够发现Member和Order中的属性曾经被映射到MemberOrderDto中去了。

进阶应用

通过下面的根本应用,大家曾经能够玩转MapStruct了,上面咱们再来介绍一些进阶的用法。

应用依赖注入

下面咱们都是通过Mapper接口中的INSTANCE实例来调用办法的,在Spring中咱们也是能够应用依赖注入的。

  • 想要应用依赖注入,咱们只有将@Mapper注解的componentModel参数设置为spring即可,这样在生成接口实现类时,MapperStruct会为其增加@Component注解;
<code class="java">/**
 * 会员对象映射(依赖注入)
 * Created by macro on 2021/10/21.
 */
@Mapper(componentModel = "spring")
public interface MemberSpringMapper {
    @Mapping(source = "phone",target = "phoneNumber")
    @Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
    MemberDto toDto(Member member);
}
  • 接下来在Controller中应用@Autowired注解注入即可应用;
<code class="java">/**
 * MapStruct对象转换测试Controller
 * Created by macro on 2021/10/21.
 */
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {

    @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);
    }
}
  • 在Swagger中调用接口测试下,能够发现与之前一样能够失常应用。

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

应用MapStruct映射属性时,咱们能够设置属性为常量或者默认值,也能够通过Java中的办法编写表达式来主动生成属性。

  • 例如上面这个商品类Product对象;
<code class="java">/**
 * 商品
 * Created by macro on 2021/10/12.
 */
@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;
}
  • 咱们想把Product转换为ProductDto对象,id属性设置为常量,count设置默认值为1,productSn设置为UUID生成;
<code class="java">/**
 * 商品Dto
 * Created by macro on 2021/10/12.
 */
@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;
}
  • 创立ProductMapper接口,通过@Mapping注解中的constantdefaultValueexpression设置好映射规定;
<code class="java">/**
 * 商品对象映射
 * Created by macro on 2021/10/21.
 */
@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);
}
  • 接下来在Controller中创立测试接口,间接通过接口中的INSTANCE实例调用转换方法toDto
<code class="java">/**
 * MapStruct对象转换测试Controller
 * Created by macro on 2021/10/21.
 */
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
    @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);
    }
}
  • 在Swagger中调用接口测试下,对象曾经胜利转换。

在映射前后进行自定义解决

MapStruct也反对在映射前后做一些自定义操作,相似AOP中的切面。

  • 因为此时咱们须要创立自定义解决办法,创立一个抽象类ProductRoundMapper,通过@BeforeMapping注解自定义映射前操作,通过@AfterMapping注解自定义映射后操作;
<code class="java">/**
 * 商品对象映射(自定义解决)
 * Created by macro on 2021/10/21.
 */
@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.ZERO);
        }
    }

    @AfterMapping
    public void afterMapping(@MappingTarget ProductDto productDto){
        //映射后设置以后工夫为createTime
        productDto.setCreateTime(new Date());
    }
}
  • 接下来在Controller中创立测试接口,间接通过Mapper中的INSTANCE实例调用转换方法toDto
<code class="java">/**
 * MapStruct对象转换测试Controller
 * Created by macro on 2021/10/21.
 */
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
    
    @ApiOperation(value = "在映射前后进行自定义解决")
    @GetMapping("/customRoundMapping")
    public CommonResult customRoundMapping() {
        List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
        Product product = productList.get(0);
        product.setPrice(new BigDecimal(-1));
        ProductDto productDto = ProductRoundMapper.INSTANCE.toDto(product);
        return CommonResult.success(productDto);
    }
}
  • 在Swagger中调用接口测试下,能够发现曾经利用了自定义操作。

解决映射异样

代码运行难免会出现异常,MapStruct也反对解决映射异样。

  • 咱们须要先创立一个自定义异样类;
<code class="java">/**
 * 商品验证异样类
 * Created by macro on 2021/10/22.
 */
public class ProductValidatorException extends Exception{
    public ProductValidatorException(String message) {
        super(message);
    }
}
  • 而后创立一个验证类,当price设置小于0时抛出咱们自定义的异样;
<code class="java">/**
 * 商品验证异样处理器
 * Created by macro on 2021/10/22.
 */
public class ProductValidator {
    public BigDecimal validatePrice(BigDecimal price) throws ProductValidatorException {
        if(price.compareTo(BigDecimal.ZERO)<0){
            throw new ProductValidatorException("价格不能小于0!");
        }
        return price;
    }
}
  • 之后咱们通过@Mapper注解的uses属性使用验证类;
<code class="java">/**
 * 商品对象映射(解决映射异样)
 * Created by macro on 2021/10/21.
 */
@Mapper(uses = {ProductValidator.class},imports = {UUID.class})
public interface ProductExceptionMapper {
    ProductExceptionMapper INSTANCE = Mappers.getMapper(ProductExceptionMapper.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) throws ProductValidatorException;
}
  • 而后在Controller中增加测试接口,设置price-1,此时在进行映射时会抛出异样;
<code class="java">/**
 * MapStruct对象转换测试Controller
 * Created by macro on 2021/10/21.
 */
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
    @ApiOperation(value = "解决映射异样")
    @GetMapping("/exceptionMapping")
    public CommonResult exceptionMapping() {
        List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
        Product product = productList.get(0);
        product.setPrice(new BigDecimal(-1));
        ProductDto productDto = null;
        try {
            productDto = ProductExceptionMapper.INSTANCE.toDto(product);
        } catch (ProductValidatorException e) {
            e.printStackTrace();
        }
        return CommonResult.success(productDto);
    }
}
  • 在Swagger中调用接口测试下,发现运行日志中曾经打印了自定义异样信息。

总结

通过上面对MapStruct的应用体验,咱们能够发现MapStruct远比BeanUtils要弱小。当咱们想实现比较复杂的对象映射时,通过它能够省去写Getter、Setter办法的过程。 当然下面只是介绍了MapStruct的一些罕用性能,它的性能远不止于此,感兴趣的敌人能够查看下官网文档。

参考资料

官网文档:https://mapstruct.org/documen…

我的项目源码地址

https://github.com/macrozheng&#8230;

本文 GitHub https://github.com/macrozheng/mall-learning 曾经收录,欢送大家Star!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:关于java:干掉-BeanUtils试试这款-Bean-自动映射工具真心强大

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址