麻了,不要再動不動就BeanUtil.copyProperties!!!

2023-04-16 12:00:37

前言

最近專案上要求升級一個工具包hutool的版本,以解決安全漏洞問題,這不升級還好,一升級反而捅出了更大的簍子,究竟是怎麼回事呢?

事件回顧

我們專案原先使用的hutool版本是5.7.2,在程式碼中,我們的資料傳輸物件DTO和資料實體物件中大量使用了工具包中的BeanUtil.copyProperties(), 大體程式碼如下:

  1. 資料傳輸物件
@Data
@ToString
public class DiagramDTO {

    // 前端生產的字串id
    private String id;

    private String code;

    private String name;
}
  1. 資料實體物件
@Data
@ToString
public class Diagram {

    private Integer id;

    private String code;

    private String name;
}
  1. 業務邏輯
public class BeanCopyTest {

    public static void main(String[] args) {
        // 前端傳輸的物件
        DiagramDTO diagramDTO = new DiagramDTO();
        // 如果前端傳入的id事包含e的,升級後就會報錯
        diagramDTO.setId("3em3dgqsgmn0");
        diagramDTO.setCode("d1");
        diagramDTO.setName("圖表");

        Diagram diagram = new Diagram();
        // 關鍵點,資料拷貝
        BeanUtil.copyProperties(diagramDTO, diagram);
        System.out.println("資料實體物件:" + diagram);
        //設定id為空,自增
        diagram.setId(null);
        //儲存到資料庫中 TODO
        //diagramMapper.save(diagram);
    }
}

升級前,hutool是5.7.2版本下,執行結果如下圖。

  • BeanUtil.copyProperties雖然欄位型別不一樣,但是做了相容處理,所以業務沒有影響業務邏輯。

升級後,hutool是5.8.8版本,執行結果如下圖所示:

  • 執行報錯,因為升級後的版本修改了實現,增加了下面的邏輯,如果包含E, 就會拋錯,從而影響了業務邏輯,同時這個id是否包含e又是隨機因素,到了生產才發現,就悲劇了。

分析探討

我發現大部分人寫程式碼都喜歡偷懶,在上面的場景中,雖然BeanUtil.copyProperties用的一時爽,但有時候帶來的後果是很嚴重的,所以很不推薦這種方式。為什麼這麼說呢?

比如團隊中的某些人偷偷改了資料傳輸物件DTO,比如修改了型別、刪去了某個欄位。用BeanUtil.copyProperties的方式壓根無法在編譯階段發現,更別提修改的影響範圍了,這就只能把風險暴露到生產上去了。那有什麼更好的方法呢?

推薦方案

  1. 原始的getset方式

我是比較推崇這種做法的,比如現在DiagramDTO刪去某個欄位,編譯器就會報錯,就會引起你的注意了,讓問題提前暴露,無處遁形。

你可能覺得站著說話不腰疼,欄位少好,如果欄位很多還不得寫死啊,我這裡推薦一個IDEA的外掛,可以幫你智慧生成這樣的程式碼。

話不多說,自己玩兒去~~

  1. 使用開源庫ModelMapper

ModelMapper是一個開源庫,可以很方便、簡單地將物件從一種型別對映到另一種型別,底層是通過反射來自動確定物件之間的對映,還可以自定義對映規則。

 private static void testModelMapper() {
        ModelMapper modelMapper = new ModelMapper();
        DiagramDTO diagramDTO = new DiagramDTO();
        diagramDTO.setId("3em3dgqsgmn0");
        diagramDTO.setCode("d1");
        diagramDTO.setName("圖表");
        Diagram diagram = modelMapper.map(diagramDTO, Diagram.class);
    }
  1. 使用開源庫MapStruct

MapStruct也是Java中另外一個用於對映物件很流行的開源工具。它是在編譯階段生成對應的對映程式碼,相對於ModelMapper底層放射的方案,效能更好。

@Mapper
public interface DiagramMapper {
    DiagramMapper INSTANCE = Mappers.getMapper(DiagramMapper.class);

    DiagramDTO toDTO(Diagram diagram);

    Diagram toEntity(DiagramDTO diagram);
}

private static void testMapStruct() {
    DiagramDTO diagramDTO = new DiagramDTO();
    diagramDTO.setId("3em3dgqsgmn0");
    diagramDTO.setCode("d1");
    diagramDTO.setName("圖表");
    Diagram diagram = DiagramMapper.INSTANCE.toEntity(diagramDTO);
}
  • DiagramMapper介面使用了@Mapper註解,用來表明使用MapStruct處理
  • MapStruct中更多高階特性大家自己探索一下。

總結

小結一下,物件在不同層之間進行轉換對映,很不建議使用BeanUtil.copyProperties這種方式,更加推薦使用原生的set, get方式,不容易出錯。當然這不是將BeanUtil.copyProperties一棒子打死,毫無用武之地,在特定場景,比如方法內部物件的轉換等影響小的範圍還是很方便的,如果你有其他的想法,也可以留下你的想法,一起探討交流。

歡迎關注個人公眾號【JAVA旭陽】交流學習!!