VL45 非同步FIFO 很經典的手撕題,這道題要求產生的格雷碼要在本時鐘域中打一拍,其實不打也沒關係。 主要要記住 1、bin2gray的方法:右移一位與移位前異或; 2、格雷碼比較方法:空:讀指針格雷碼和寫指針同步過來的格雷碼相同;滿:寫指針格雷碼高兩位與讀指針同步過來的格雷碼正好相反,低位相同。 ...
背景
在現在流行的系統設計中,一般會將對象模型劃分為多個層次,例如 VO、DTO、PO、BO 等等。這同時也產生了一個問題,經常需要進行不同層級的模型之間相互轉換。
針對這種問題,目前常會採用三種方案:
- 調用每個欄位的 getter/setter 進行賦值。這個過程,枯燥且乏味,容易出錯的同時,極易容易造成代碼行數迅速膨脹,可閱讀性差。
- apache-commons、Spring 等提供的
BeanUtil
工具類,這種工具類使用非常方便,一行代碼即可實現映射。但其內部採用反射的方式來實現映射,性能低下,出現問題時,調試困難,當需要個性化轉換時,配置麻煩,非常不建議使用,特別是對於性能要求比較高的程式中。 - mapstruct:這個框架是基於 Java 註釋處理器,定義一個轉換介面,在編譯的時候,會根據介面類和方法相關的註解,自動生成轉換實現類。生成的轉換邏輯,是基於 getter/setter 方法的,所以不會像
BeanUtil
等消耗其性能。
上面的三種方法中,最優秀的莫屬 mapstruct 了,當然,美中不足的就是,當系統較為複雜,對象較多且結構複雜,又或者有的項目設計中會定義多層對象模型(如 DDD 領域設計),需要定義較多的轉換介面和轉換方法,這也是一些開發者放棄 Mapstruct 的主要原因。
這裡,就要給大家介紹一個 Mapstruct 的增強包 —— Mapstruct Plus,一個註解,可以生成兩個類之間的轉換介面,使 Java 類型轉換更加便捷、優雅,徹底拋棄 BeanUtils。
快速開始
下麵演示如何使用 MapStruct Plus 來映射兩個對象。
假設有兩個類 UserDto
和 User
,分別表示數據層對象和業務層對象:
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 步驟非常簡單。
- 給需要轉換的類添加
AutoMapper
註解 - 獲取
Converter
實例,調用convert
方法即可
特性
完全相容 Mapstruct
Mapst實現了增強操作,如果之前已經使用了 Mapstruct,可以直接替換相關依賴。
單註解即可實現兩個對象相互轉換
例如快速開始中,只在 User
類上面增加註解 @AutoMapper
,Mapstruct Plus 除了會生成 User
-> UserDto
的轉換介面,預設還會生成 UserDto
-> User
的轉換介面。
編譯後,可以查看生成的類,如下:
自定義對象類型的屬性自動轉換
當兩個需要轉換的對象 A
和 ADto
,其中的屬性 B
和 BDto
是自定義類型,並且也定義了 @AutoMapper
註解的話,在生成轉換 A
和 ADto
時,會自動依賴屬性 B
和 BDto
的轉換介面,實現其相應的屬性轉換。
例如:
分別有兩組對象模型:汽車(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
註解,支持配置多個目標對象,進行轉換。
例如:有三個對象,User
、UserDto
、UserVO
,User
分別要和其他兩個對象進行轉換。則可以按如下配置:
@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;
}
自定義數字格式化
當數字類型(double
、long
、BigDecimal
)轉換為 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
說再見了。