相信咱们关于VO、DTO、PO等都不陌生,其实都是各执其责,在不同的效果域中有着属于自己单独的效果。

先简略介绍一下它们吧

这儿我贴出一张图来让咱们直观的看出来它们各自的效果域

本来不止有BeanUtils啊,来看看更优异的MapStruct吧
DTO(Data Transfer Object)数据传输方针

这个传输通常指的前后端之间的传输 DTO是一个比较特殊的方针,他有两种存在形式: 一种是前端和后端交互所运用的方针,另一种是微服务之间的一种传输方针,咱们一般也是用DTO来进行传输,但是假如是一个单体项目仅仅划分为不同的模块的话,模块间的传输就不是用DTO了,这个时分或许便是图中的BO,也或许是咱们各自项目中的entity或许domain包中的实体,其实这些也仅仅看咱们的个人见解来知道并去理解。

VO(Value Object)值方针

VO便是展示用的数据,不管展示办法是网页,仍是客户端,仍是APP,只要是这个东西是让人看到的,这就叫VO,这个咱们都很理解,反正便是咱们的接口回来给前端的方针都是用VO来回来,跟DTO不一样的是,VO是咱们回来给前端,DTO是咱们早年端接收的时分用的,即一个是入参,一个是回来结果

PO(Persistant Object)持久方针

PO比较好理解,简略说PO便是数据库中的记录,一个PO的数据结构对应着库中表的结构,表中的一条记录便是一个PO方针,通常PO里面除了get,set之外没有其他办法,关于PO来说,数量是相对固定的,必定不会超过数据库表的数量,等同于BO,这俩概念是共同的

那么在运用这些方针的过程中咱们就避免不了去进行方针的转化,最开端的阶段咱们或许便是如下操作进行一个特点一个特点的进行set(),将同含义的字段的值由DTO set到BO,再由BO set到PO,终究存入数据库中,但是当这种操作变得频频的时分咱们就会很厌烦这种编写,很糟蹋咱们的时刻,拉低咱们的开发功率,削减咱们的摸鱼时刻,当然有的公司或许是经过看代码量来评绩效,这个时分咱们仍是这种办法来写比较合适哈哈哈哈。

我记住我刚工作的时分,我看见了他人经过BeanUtils来进行特点仿制,似乎发现了新大陆,这个真的好用,一两行代码就完事,直接把相同名称及类型的字段给赋值到新方针中,真的是太爱了。所以期间用到方针仿制我也基本都是用的这个东西类,但是后面个人在一些项目中发现了另一个办法,当时我也不知道是干嘛的,后面再看的时分发现也是特点仿制的效果,这个时分我或许就想了想这个mapstructBeanUtils有什么差异,下面我来贴一下源码图吧,咱们能直观的看出来二者的一个很大的差异:

BeanUtils 源码

本来不止有BeanUtils啊,来看看更优异的MapStruct吧
咱们能够看见,BeanUtils的源码中是运用了反射然后经过遍历再加上不断的判别来进行特点赋值,不得不说,这个东西类的开发者真牛逼,相信他在开发这个东西类的过程中也很苦楚吧,处理了许多bug吧哈哈哈

MapStruct 编译后生成的代码

本来不止有BeanUtils啊,来看看更优异的MapStruct吧
咱们不难看出,编译后的代码其实便是咱们经常写的set()办法,不同特点或许类型会主动进行判别赋值。

由此可见二者最明显的差异便是运行功率上的差异,反射较于咱们自己set的话会花费更多的时刻,况且遍历里面又套了层层判别,但是MapStruct便是直接经过最简略的联系映射逐一 set 进去,功率上会快的多。

这儿我贴出一个根据BeanUtils上封装的一个方针转化东西类,方便咱们进行流式运用,比原生的BeanUtils更好用。

import org.springframework.beans.BeanUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
 * 转化方针东西
 */
public class BeanConvertUtils extends BeanUtils {
    public static <S, T> T convertTo(S source, Supplier<T> targetSupplier) {
        return convertTo(source, targetSupplier, null);
    }
    /**
     * 转化方针
     *
     * @param source         源方针
     * @param targetSupplier 方针方针供应方
     * @param callBack       回调办法
     * @param <S>            源方针类型
     * @param <T>            方针方针类型
     * @return 方针方针
     */
    public static <S, T> T convertTo(S source, Supplier<T> targetSupplier, ConvertCallBack<S, T> callBack) {
        if (null == source || null == targetSupplier) {
            return null;
        }
        T target = targetSupplier.get();
        copyProperties(source, target);
        if (callBack != null) {
            callBack.callBack(source, target);
        }
        return target;
    }
    public static <S, T> List<T> convertListTo(List<S> sources, Supplier<T> targetSupplier) {
        return convertListTo(sources, targetSupplier, null);
    }
    /**
     * 转化方针
     *
     * @param sources        源方针list
     * @param targetSupplier 方针方针供应方
     * @param callBack       回调办法
     * @param <S>            源方针类型
     * @param <T>            方针方针类型
     * @return 方针方针list
     */
    public static <S, T> List<T> convertListTo(List<S> sources, Supplier<T> targetSupplier, ConvertCallBack<S, T> callBack) {
        if (null == sources || null == targetSupplier) {
            return null;
        }
        List<T> list = new ArrayList<>(sources.size());
        for (S source : sources) {
            T target = targetSupplier.get();
            copyProperties(source, target);
            if (callBack != null) {
                callBack.callBack(source, target);
            }
            list.add(target);
        }
        return list;
    }
    /**
     * 回调接口
     *
     * @param <S> 源方针类型
     * @param <T> 方针方针类型
     */
    @FunctionalInterface
    public interface ConvertCallBack<S, T> {
        void callBack(S s, T t);
    }
}

下面是测验类

@Test
public void test04(){
    //==================== copy  Object =====================
    User user = new User().setAge("20").setName("xxw");
    // user = null;     //当user方针为空的时分,copy办法不会报错,也只会回来null
    System.out.println("仿制前的数据:" user);
    //一般写法
    UserDTO userDTO = BeanConvertUtils.convertTo(user, UserDTO::new);
    System.out.println("仿制后的数据:" userDTO);
    //lambda写法,能够对不同类型的字段进行手动赋值
    UserDTO dto = BeanConvertUtils.convertTo(user, UserDTO::new, (u, d) -> d.setAge(Integer.valueOf(u.getAge())));
    System.out.println("仿制后的数据:" dto);
    System.out.println("=================================================");
    //==================== copy  List<Object> =====================
    User user1 = new User().setAge("22").setName("xw");
    ArrayList<User> users = Lists.newArrayList(user1);
    System.out.println("仿制前的数据:" users);
    //一般写法
    List<UserDTO> dtos = BeanConvertUtils.convertListTo(users, UserDTO::new);
    System.out.println("仿制后的数据:" dtos);
    //lambda写法,能够对不同类型的字段进行手动赋值
    List<UserDTO> dtoList = BeanConvertUtils.convertListTo(users, UserDTO::new, (u, d) -> d.setAge(Integer.valueOf(u.getAge())));
    System.out.println("仿制后的数据:" dtoList);
}

输出结果

仿制前的数据:User(userId=null, name=xxw, age=20)
仿制后的数据:UserDTO(name=xxw, age=null)
仿制后的数据:UserDTO(name=xxw, age=20)
=================================================
仿制前的数据:[User(userId=null, name=xw, age=22)]
仿制后的数据:[UserDTO(name=xw, age=null)]
仿制后的数据:[UserDTO(name=xw, age=22)]

运用MapStruct 首要有两个需求换转的方针

@Data
@Accessors(chain = true)
public class BeanDto {
    private String name;
    private Integer age;
    private String time;
    private List<Integer> list;
    private Set<String> set;
}
@Data
@Accessors(chain = true)
public class BeanPo {
    private String name;
    private String age;
    private String newTime;
    private List<String> list1;
    private Set<String> set1;
}

第一步先引进依靠

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
</dependency>

根据方针创建一个转化接口

import com.google.common.collect.Lists;
import com.itself.mapstruct.bean.BeanDto;
import com.itself.mapstruct.bean.BeanPo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
import org.mockito.internal.util.collections.Sets;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @Author: duJi
 * @Date: 2024-01-19
 **/
@Mapper
public interface BeanConvert{
    BeanConvert INSTANCE = Mappers.getMapper( BeanConvert.class );
    BeanDto poToDto(BeanPo po);
    @Mappings({
            @Mapping(source = "time", target = "newTime"),//不同的字段名称能够经过这种办法来进行主动映射
            @Mapping(source = "set", target = "set1"),//不同的字段名称能够经过这种办法来进行主动映射
            @Mapping(target = "age",qualifiedByName = "intToString"),//运用自定义办法来对不同类型的字段进行转化映射
            @Mapping(source = "list", target = "list1",qualifiedByName = "intToStringOnList")//运用自定义办法来对不同类型的字段调集进行转化映射
    })
    BeanPo dtoToPo(BeanDto dto);
    @Named("intToString")
    static String  intToString(Integer num){
        return String.valueOf(num);
    }
    @Named("intToStringOnList")
    static List<String> intToString(List<Integer> nums){
        return nums.stream().map(String::valueOf).collect(Collectors.toList());
    }
    static void main(String[] args) {
        BeanDto dto = new BeanDto().setName("dto").setAge(1).setTime("2022").setList(Lists.newArrayList(1,2)).setSet(Sets.newSet("2"));
        BeanPo beanPo = BeanConvert.INSTANCE.dtoToPo(dto);
        System.out.println(beanPo);
    }
}

当咱们两个方针中相同的字段类型但是不同的名称的时分能够运用@Mapping注解进行映射,假如是不同的字段类型的话咱们能够运用自定义办法去对字段类型进行转化,运用@Named注解进行调用自定义办法来转化并赋值,我上面的demo也基本上覆盖了或许会呈现的场景,咱们可自行下去体会。

总的来说,假如对功能要求不高的话BeanUtils用着挺方便,不用引进依靠单独建转化接口及办法,对功能有要求的话就手动 set 或许运用MapStruct来进行方针转化,这些办法运用的过程中仅有需求留意的便是咱们要仔细检查映射字段的名称及类型是否共同,不共同的话就容易报错或许没有成功赋值。