徹底幹掉 BeanUtils,最優雅的 Mapstruct 增強工具全新出爐

来源:https://www.cnblogs.com/linpeilie/archive/2023/03/05/17181933.html
-Advertisement-
Play Games

VL45 非同步FIFO 很經典的手撕題,這道題要求產生的格雷碼要在本時鐘域中打一拍,其實不打也沒關係。 主要要記住 1、bin2gray的方法:右移一位與移位前異或; 2、格雷碼比較方法:空:讀指針格雷碼和寫指針同步過來的格雷碼相同;滿:寫指針格雷碼高兩位與讀指針同步過來的格雷碼正好相反,低位相同。 ...


背景

在現在流行的系統設計中,一般會將對象模型劃分為多個層次,例如 VO、DTO、PO、BO 等等。這同時也產生了一個問題,經常需要進行不同層級的模型之間相互轉換。

針對這種問題,目前常會採用三種方案:

  1. 調用每個欄位的 getter/setter 進行賦值。這個過程,枯燥且乏味,容易出錯的同時,極易容易造成代碼行數迅速膨脹,可閱讀性差。
  2. apache-commons、Spring 等提供的 BeanUtil 工具類,這種工具類使用非常方便,一行代碼即可實現映射。但其內部採用反射的方式來實現映射,性能低下,出現問題時,調試困難,當需要個性化轉換時,配置麻煩,非常不建議使用,特別是對於性能要求比較高的程式中。
  3. mapstruct:這個框架是基於 Java 註釋處理器,定義一個轉換介面,在編譯的時候,會根據介面類和方法相關的註解,自動生成轉換實現類。生成的轉換邏輯,是基於 getter/setter 方法的,所以不會像 BeanUtil 等消耗其性能。

上面的三種方法中,最優秀的莫屬 mapstruct 了,當然,美中不足的就是,當系統較為複雜,對象較多且結構複雜,又或者有的項目設計中會定義多層對象模型(如 DDD 領域設計),需要定義較多的轉換介面和轉換方法,這也是一些開發者放棄 Mapstruct 的主要原因。

這裡,就要給大家介紹一個 Mapstruct 的增強包 —— Mapstruct Plus,一個註解,可以生成兩個類之間的轉換介面,使 Java 類型轉換更加便捷、優雅,徹底拋棄 BeanUtils。

快速開始

下麵演示如何使用 MapStruct Plus 來映射兩個對象。

假設有兩個類 UserDtoUser,分別表示數據層對象和業務層對象:

  • UserDto
public class UserDto {
    private String username;
    private int age;
    private boolean young;

    // getter、setter、toString、equals、hashCode
}
  • User
public class User {
    private String username;
    private int age;
    private boolean young;

    // getter、setter、toString、equals、hashCode
}

添加依賴

引入 mapstruct-plus-spring-boot-starter 依賴:

<properties>
    <mapstruct-plus.version>1.1.3</mapstruct-plus.version>
</properties>
<dependencies>
    <dependency>
        <groupId>io.github.linpeilie</groupId>
        <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
        <version>${mapstruct-plus.version}</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>io.github.linpeilie</groupId>
                        <artifactId>mapstruct-plus-processor</artifactId>
                        <version>${mapstruct-plus.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

指定對象映射關係

User 或者 UserDto 上面增加註解 —— @AutoMapper,並設置 targetType 為對方類。

例如:

@AutoMapper(target = UserDto.class)
public class User {
    // ...
}

測試

@SpringBootTest
public class QuickStartTest {

    @Autowired
    private Converter converter;

    @Test
    public void test() {
        User user = new User();
        user.setUsername("jack");
        user.setAge(23);
        user.setYoung(false);

        UserDto userDto = converter.convert(user, UserDto.class);
        System.out.println(userDto);    // UserDto{username='jack', age=23, young=false}

        assert user.getUsername().equals(userDto.getUsername());
        assert user.getAge() == userDto.getAge();
        assert user.isYoung() == userDto.isYoung();

        User newUser = converter.convert(userDto, User.class);

        System.out.println(newUser);    // User{username='jack', age=23, young=false}

        assert user.getUsername().equals(newUser.getUsername());
        assert user.getAge() == newUser.getAge();
        assert user.isYoung() == newUser.isYoung();
    }

}

小結

引入依賴後,使用 Mapstruct Plus 步驟非常簡單。

  1. 給需要轉換的類添加 AutoMapper 註解
  2. 獲取 Converter 實例,調用 convert 方法即可

特性

完全相容 Mapstruct

Mapst實現了增強操作,如果之前已經使用了 Mapstruct,可以直接替換相關依賴。

單註解即可實現兩個對象相互轉換

例如快速開始中,只在 User 類上面增加註解 @AutoMapper,Mapstruct Plus 除了會生成 User -> UserDto 的轉換介面,預設還會生成 UserDto -> User 的轉換介面。

編譯後,可以查看生成的類,如下:

image.png

自定義對象類型的屬性自動轉換

當兩個需要轉換的對象 AADto,其中的屬性 BBDto 是自定義類型,並且也定義了 @AutoMapper 註解的話,在生成轉換 AADto 時,會自動依賴屬性 BBDto 的轉換介面,實現其相應的屬性轉換。

例如:
分別有兩組對象模型:汽車(Car) 和座椅配置(SeatConfiguration),其中 Car 依賴 SeatConfiguration。

兩組對象結構一致,只不過代表層級不一樣,這裡拿 dto 層的對象定義舉例:

  • CarDto
@AutoMapper(target = Car.class)
public class CarDto {
    private SeatConfigurationDto seatConfiguration;
}
  • SeatConfiguration
@AutoMapper(target = SeatConfiguration.class)
public class SeatConfigurationDto {
    private int seatCount;
}

測試:

@Test
public void carConvertTest() {
    CarDto carDto = new CarDto();
    SeatConfigurationDto seatConfigurationDto = new SeatConfigurationDto();
    seatConfigurationDto.setSeatCount(4);
    carDto.setSeatConfiguration(seatConfigurationDto);

    final Car car = converter.convert(carDto, Car.class);
    System.out.println(car);    // Car(seatConfiguration=SeatConfiguration(seatCount=4))

    assert car.getSeatConfiguration() != null;
    assert car.getSeatConfiguration().getSeatCount() == 4;
}

單個對象對多個對象進行轉換

Mapstruct Plus 提供了 @AutoMappers 註解,支持配置多個目標對象,進行轉換。

例如:有三個對象,UserUserDtoUserVOUser 分別要和其他兩個對象進行轉換。則可以按如下配置:

@Data
@AutoMappers({
    @AutoMapper(target = UserDto.class),
    @AutoMapper(target = UserVO.class)
})
public class User {
    // ...
}

Map 轉對象

Mapstruct Plus 提供了 @AutoMapMapper 註解,支持生成 Map<String, Object> 轉換為當前類的介面。同時,還支持 map 中嵌套 Map<String, Object> 轉換為自定義類嵌套自定義類的場景。

其中,map 中的 value 支持的類型如下:

  • String
  • BigDecimal
  • BigInteger
  • Integer
  • Long
  • Double
  • Number
  • Boolean
  • Date
  • LocalDateTime
  • LocalDate
  • LocalTime
  • URI
  • URL
  • Calendar
  • Currency
  • 自定義類(自定義類也需要增加 @AutoMapMapper 註解

例如:

有如下兩個介面:

  • MapModelA
@Data
@AutoMapMapper
public class MapModelA {

    private String str;
    private int i1;
    private Long l2;
    private MapModelB mapModelB;

}
  • MapModelB
@Data
@AutoMapMapper
public class MapModelB {

    private Date date;
    
}

測試:

@Test
public void test() {
    Map<String, Object> mapModel1 = new HashMap<>();
    mapModel1.put("str", "1jkf1ijkj3f");
    mapModel1.put("i1", 111);
    mapModel1.put("l2", 11231);

    Map<String, Object> mapModel2 = new HashMap<>();
    mapModel2.put("date", DateUtil.parse("2023-02-23 01:03:23"));

    mapModel1.put("mapModelB", mapModel2);

    final MapModelA mapModelA = converter.convert(mapModel1, MapModelA.class);
    System.out.println(mapModelA);  // MapModelA(str=1jkf1ijkj3f, i1=111, l2=11231, mapModelB=MapModelB(date=2023-02-23 01:03:23))
}

支持自定義轉換

自定義屬性轉換

Mapstruct Plus 提供了 @AutoMapping 註解,該註解在編譯後,會變為 Mapstruct 中的 @Mapping 註解,已經實現了幾個常用的註解屬性。

指定不同名稱屬性欄位映射轉換

例如,Car 類中屬性 wheels 屬性,轉換 CarDto 類型時,需要將該欄位映射到 wheelList 上面,可以在 wheels 上面增加如下註解:

@AutoMapper(target = CarDto.class)
public class Car {
    @AutoMapping(target = "wheelList")
    private List<String> wheels;
}

自定義時間格式化

在將 Date 類型的屬性,轉換為 String 類型時,可以通過 dateFormat 來指定時間格式化:

@AutoMapper(target = Goods.class)
public class GoodsDto {

    @AutoMapping(target = "takeDownTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date takeDownTime;

}

自定義數字格式化

當數字類型(doublelongBigDecimal)轉換為 String 類型時,可以通過 numberFormat 指定 java.text.DecimalFormat 所支持的格式:

@AutoMapper(target = Goods.class)
public class GoodsDto {

    @AutoMapping(target = "price", numberFormat = "$#.00")
    private int price;

}

自定義 Java 表達式

@AutoMapping 提供了 expression 屬性,支持配置一段可執行的 Java 代碼,來執行具體的轉換邏輯。

例如,當一個 List<String> 的屬性,想要轉換為用 , 分隔的字元串時,可以通過該配置,來執行轉換邏輯:

@AutoMapper(target = UserDto.class)
public class User {

    @AutoMapping(target = "educations", expression = "java(java.lang.String.join(",", source.getEducationList()))")
    private List<String> educationList;

}

自定義類型轉換器

@AutoMapping 註解提供了 uses 屬性,引入自定義的類型轉換器,來提供給當前類轉換時使用。

實例場景:

項目中會有字元串用 , 分隔,在一些類中,需要根據逗號拆分為字元串集合。針對於這種場景,可以有兩種方式:首先可以指定欄位映射時的表達式,但需要對每種該情況的欄位,都添加表達式,複雜且容易出錯。

第二,就可以自定義一個類型轉換器,通過 uses 來使用

public interface StringToListString {
    default List<String> stringToListString(String str) {
        return StrUtil.split(str);
    }
}

@AutoMapper(target = User.class, uses = StringToListStringConverter.class)
public class UserDto {

    private String username;
    private int age;
    private boolean young;
    @AutoMapping(target = "educationList")
    private String educations;
    // ......
}

測試:


@SpringBootTest
public class QuickStartTest {

    @Autowired
    private Converter converter;

    @Test
    public void ueseTest() {
        UserDto userDto = new UserDto();
        userDto.setEducations("1,2,3");

        final User user = converter.convert(userDto, User.class);
        System.out.println(user.getEducationList());    // [1, 2, 3]

        assert user.getEducationList().size() == 3;
    }
}

更多

更多特性,可以查看官方文檔

結束語

Mapstruct Plus 相對於 Mapstruct 來說,繼承了其高性能的特點,同時增強了其便攜性和快速開發的特性,在系統模型設計較好(屬性及類型基本一致)的情況下,開發成本極低,是時候和 BeanUtils 說再見了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這篇文章主要描述如何進行消息隊列產品選型,包括產品選型需要考慮的因素、三種比較流行的消息隊列產品的優缺點以及如何根據我們的使用場景選擇合適的消息隊列產品。 ...
  • OneAPM 摘要:此篇文章主要介紹Java8 Lambda 表達式產生的背景和用法,以及 Lambda 表達式與匿名類的不同等。本文系OneAPM工程師編譯整理。 Java是一流的面向對象語言,除了部分簡單數據類型,Java 中的一切都是對象,即使數組也是一種對象,每個類創建的實例也是對象。在 J ...
  • 在企業的商業活動中,訂單是指交易雙方的產品或服務交易意向。交易下單負責創建這個交易雙方的產品或服務交易意向,有了這個意向後,買方可以付款,賣方可以發貨。 在電商場景下,買賣雙方沒有面對面交易,許多情況下需要通過超時處理自動關閉訂單 ...
  • 1. 編譯閾值 1.1. 一旦代碼執行到一定次數,就達到了它的編譯閾值,編譯器就會認為它有足夠的信息來編譯代碼 1.2. 在當前的JVM中,優化閾值的意義不大 1.2.1. 從JDK 7以及更早期遺留下來的 1.3. -XX:CompileThreshold=N 1.3.1. 當禁用分層編譯時有效 ...
  • 0. RSocket 簡介 採用二進位點對點數據傳輸,主要應用於分散式架構之中,是一種基於Reactive Stream規範標準實現的新的通信協議。 參考阿裡雲開發者社區的介紹 相關文檔和資料: RSocket By Example rsocket-java 原生庫例子 Spring RSocket ...
  • 環境 odoo-14.0.post20221212.tar context用法總結 獲取上下文 >>> self.env.context # 返回字典數據,等價於 self._context {'lang': 'en_US', 'tz': 'Europe/Brussels'} >>> self._c ...
  • Qt 學習筆記全系列傳送門: 【本章】Qt 學習筆記 - 第一章 - 快速開始、信號與槽 Qt 學習筆記 - 第二章 - 添加圖片、佈局、界面切換 1、Qt 工程創建 使用 Qt Creator 創建 Qt 工程,不能包含中文目錄 2、工程文件(pro 文件) # # # Project creat ...
  • 項目實現01 1.功能01-搭建Vue前端工程 1.1需求分析 使用Vue3的腳手架vue-cli工具,創建ssm的前端項目基礎開發環境 Vue-cli主要的功能是自動生成Vue的項目模板,提高開發效率 1.2代碼實現 1.2.1搭建vue工程 以下命令使用管理員許可權 (1)先下載node.js L ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...