你的bean拷贝工具好用吗?

 关注公众号【1024个为什么】,及时接收最新推送文章! 

最近又是一堆前后端交互的接口,参数的接收、转换,重复编码真的很崩溃。

究其根本原因,还是前后端语言不统一,对基本数据类型的支持有所差异造成的。就形成了 vo 一般都采用 String 类型来接收这些有差异的数据类型的解决方案,但 dto 和数据库需要的类型又是其真正对应的数据类型。

所以本文主要讨论 vo 和 dto 之间的拷贝问题。

||  诉求

| 诉求一:同名、不同类型的属性,在拷贝时也能赋值

主要是Long、BigDecimal等类型前后端精度问题,vo 里一般都定义为 String 类型,但类型不同的属性在拷贝时又会被略过。

| 诉求二:日期格式化能够灵活适应

页面可能展示年月日,年月日时分秒、时分...,后端是一个Date类型,怎么能适应不同vo呢?

| 诉求三:枚举自动映射

库里存的 1、2,页面要展示成 男、女,每次都要判断赋值?

||  现有方案

| 规规矩矩,手写拷贝

每一对 vo 和 dto 都写一个拷贝方法,对需要单独处理的属性,逐一处理。


  public void voToDto(Vo vo, Dto dto){
    dto.setOrderId(Long.valueOf(vo.getOrderId()));
    dto.setServiceTime(new SimpleDateFormat().parse(vo.getServiceTime()), );
    ...
  }

  public void dtoToVo(Dto dto, Vo vo){
//    if(dto.getSex() == 1){
//      vo.setSex("男");
//    }    
    vo.setSex(SexEnum.getDescByCode(dto.getSex()));
    vo.setOrderId(String.valueOf(dto.getOrderId()));
    vo.setServiceTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(dto.getServiceTime()));
    ...      
  }

缺点:费时费力,无任何复用可言
优点:前面的三点诉求都能满足

| 三方工具 + 改写setter方法

vo 多声明一套 String 类型的属性,并且改写 setter 方法,在 setter 方法中做特殊处理。

 private Long orderId;
  private String orderIdStr;

  private Integer sex;
  private Integer sexStr;

  public void setOrderId(Long orderId) {
    this.orderId = orderId;
    this.orderIdStr = String.valueOf(orderId);
  }

  public void setSex(Integer sex) {
    this.sex = sex;
    this.sexStr = SexEnum.getDescByCode(sex);
  }

缺点:只省去了同名同类型属性的赋值工作量,其他的还是没省去,也无复用可言
优点:前面的三点诉求都能满足

| 三方工具 + 重写转换器

三方工具默认只支持同名同类型属性间的赋值,毕竟不同类型属性赋值存在类型转换异常的风险,所以三方工具的做法也是合理的。

三方工具基本也都提供了重写转换逻辑的功能,可以重写转换器满足特定的需求。


public class MyConverter implements Converter {

    @Override
    public boolean convert(Object value, Class target, Object context) {
        ...
    }
}

缺点:不同的三方工具,提供的转换器不同,有一定的学习成本;可能需要写很多个转换器;诉求三无法满足
优点:vo 和 dto 无需任何改动,代码量也会缩减不少,可以满足较为复杂的需求

||  我的方案

| 思路

整体方向是解决开头提到的三个诉求。

自定义注解 + 自定义拷贝工具。

1、同名不同类型的属性,直接强转拷贝。回想我们的使用场景,都是明确的同一业务场景下的对象拷贝

2、自定义注解实现可配置的日期格式化、可配置的枚举数据源

3、拷贝逻辑中,对自定义的注解进行处理

| 上代码

自定义注解


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface FromEnum {
    Class clazz() default Enum.class;
    // code属性名
    String codeFiledName() default "code";
    // value属性名
    String valueFiledName() default "desc";
}
public @interface Pattern {
    String pattern() default "yyyy-MM-dd HH:mm:ss";
}

拷贝逻辑

public static <I, O> O convertVo(I inputObj, Class<O> outputType) {
        try {
            if (inputObj == null) {
                return null;
            }
            O k = outputType.newInstance();

            Map<String, String> inFiledMap = new HashMap<>();
            Map<String, String> outFiledMap = new HashMap<>();

            Field[] inFields = inputObj.getClass().getDeclaredFields();
            Arrays.stream(inFields).forEach((inf) -> {
                inFiledMap.put(inf.getName(), inf.getType().getSimpleName().toUpperCase());
            });
            Field[] outFields = outputType.getDeclaredFields();
            Arrays.stream(outFields).forEach((of) -> {
                outFiledMap.put(of.getName(), of.getType().getSimpleName().toUpperCase());
            });
            for (Field inField : inFields) {
                inField.setAccessible(true);
                if (!Modifier.isStatic(inField.getModifiers()) && inField.get(inputObj) != null) {
                    Field outField = outputType.getDeclaredField(inField.getName());
                    outField.setAccessible(true);
                    // String -> Date
                    if ("STRING".equals(inFiledMap.get(inField.getName())) && "DATE".equals(outFiledMap.get(inField.getName()))) {
                        // 未指定pattern
                        if (inField.getAnnotation(Pattern.class) == null) {
                            outField.set(k, DateUtil.parseDate((String) inField.get(inputObj)));
                        } else { // 指定pattern
                            outField.set(k, DateUtil.parseDate((String) inField.get(inputObj), inField.getAnnotation(Pattern.class).pattern()));
                        }
                        continue;
                    // String -> Long
                    } else if ("STRING".equals(inFiledMap.get(inField.getName())) && "LONG".equals(outFiledMap.get(inField.getName()))) {
                        outField.set(k, Long.valueOf((String) inField.get(inputObj)));
                        continue;
                    // String -> BigDecimal
                    } else if ("STRING".equals(inFiledMap.get(inField.getName())) && "BIGDECIMAL".equals(outFiledMap.get(inField.getName()))){
                        outField.set(k, new BigDecimal((String) inField.get(inputObj)));
                        continue;
                    // Long -> String
                    } else if("LONG".equals(inFiledMap.get(inField.getName())) && "STRING".equals(outFiledMap.get(inField.getName()))){
                        outField.set(k, inField.get(inputObj).toString());
                        continue;
                    // Date -> String
                    } else if("DATE".equals(inFiledMap.get(inField.getName())) && "STRING".equals(outFiledMap.get(inField.getName()))){
                        // 未指定pattern
                        if (outField.getAnnotation(Pattern.class) == null) {
                            outField.set(k, DateUtil.formatDate2String((Date) inField.get(inputObj)));
                        } else { // 指定pattern
                            outField.set(k, DateUtil.formatDate2String((Date) inField.get(inputObj), outField.getAnnotation(Pattern.class).pattern()));
                        }
                        continue;
                    //BigDecimal -> String
                    }else if ("BIGDECIMAL".equals(inFiledMap.get(inField.getName())) && "STRING".equals(outFiledMap.get(inField.getName()))){
                        outField.set(k, inField.get(inputObj).toString());
                        continue;
                    // 属性类型相同,直接赋值
                    }else if(inFiledMap.get(inField.getName()).equals(outFiledMap.get(inField.getName()))){
                        outField.set(k, inField.get(inputObj));
                        // 自动映射枚举
                        if("INTEGER".equals(inFiledMap.get(inField.getName()))
                                && "STRING".equals(outFiledMap.get(inField.getName() + "Str"))
                                && outputType.getDeclaredField(inField.getName() + "Str").getAnnotation(FromEnum.class) != null){
                            FromEnum fromEnum = outputType.getDeclaredField(inField.getName() + "Str").getAnnotation(FromEnum.class);
                            Class enumClz = fromEnum.clazz();
                            String codeFiledName = fromEnum.codeFiledName();
                            String valueFiledName = fromEnum.valueFiledName();
                            Object[] enumConstants = enumClz.getEnumConstants();
                            for (Object enumConstant : enumConstants) {
                                Method code = enumConstant.getClass().getMethod("get" + codeFiledName.toUpperCase().substring(0,1) + codeFiledName.substring(1, codeFiledName.length()));
                                Method value = enumConstant.getClass().getMethod("get" + valueFiledName.toUpperCase().substring(0,1) + valueFiledName.substring(1, valueFiledName.length()));
                                if(code != null && value != null){
                                    if (inField.get(inputObj).equals(code.invoke(enumConstant))) {
                                        Field fieldStr = outputType.getDeclaredField(inField.getName() + "Str");
                                        fieldStr.setAccessible(true);
                                        fieldStr.set(k, value.invoke(enumConstant));
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return k;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

使用

  @Pattern(pattern = "yyyy-MM-dd")
  private String serviceTime;
  @FromEnum(clazz = com.test.SexEnum.class)
  private String sex;

  BeanCopierUtil.convertVo(vo, Dto.class)

属性值合法性校验也可以加在拷贝方法的前面,更加严谨一些。

||  总结

1、工欲善其事必先利其器

2、一个够用的工具可能比一个通用的工具更合适

3、在重复造轮子的过程中也可以适当的改造一下轮子

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值