CarDTO entity = JSON.parseObject(JSON.toJSONString(carDO), CarDTO.class);這種方案因為通過生成中間json格式字符串,然後再轉化成目標對象,性能非常差,同時因為中間會生成json格式字符串,如果轉化過多,gc會非常頻繁,同時針對複雜場景支持能力不足,基本很少用。BeanUtil.copyProperties()結合手寫get、set,對於簡單的轉換直接使用BeanUtil,複雜的轉換自己手工寫get、set。該方案的痛點就在於代碼編寫效率低、冗餘繁雜還略顯醜陋,並且BeanUtil因為使用了反射invoke去賦值性能不高。只能適合bean數量較少、內容不多、轉換不頻繁的場景。org.apache.commons.beanutils.BeanUtils.copyProperties(do, entity);這種方案因為用到反射的原因,同時本身設計問題,性能比較差。集團開發規約明確規定禁止使用。org.springframework.beans.BeanUtils.copyProperties(do, entity);這種方案針對apache的BeanUtils做了很多優化,整體性能提升不少,不過還是使用反射實現比不上原生代碼處理,其次針對複雜場景支持能力不足。BeanCopier copier = BeanCopier.create(CarDO.class, CarDTO.class, false); copier.copy(do, dto, null);這種方案動態生成一個要代理類的子類,其實就是通過字節碼方式轉換成性能最好的get和set方式,重要的開銷在創建BeanCopier,整體性能接近原生代碼處理,比BeanUtils要好很多,尤其在數據量很大時,但是針對複雜場景支持能力不足。Object Mapping 技術從大的角度來說分為兩類,一類是運行期轉換,另一類則是編譯期轉換:綜合性能、成熟度、易用性、擴展性,mapstruct是比較優秀的一個框架。... <properties> <org.mapstruct.version>1.4.2.Final</org.mapstruct.version> </properties> ... <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.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>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>這裡用到了lombok簡化代碼,lombok的原理也是在編譯時去生成get、set等被簡化的代碼。@Data public class Car { private String make; private int numberOfSeats; private CarType type; }@Data public class CarDTO { private String make; private int seatCount; private String type; }@Mapper中描述映射,在編輯的時候mapstruct將會根據此描述生成實現類:@Mapper public interface CarMapper { @Mapping(source = "numberOfSeats", target = "seatCount") CarDTO CarToCarDTO(Car car); }@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount") CarDTO CarToCarDTO(Car car); }Car car = new Car(...); CarDTO carDTO = CarMapper.INSTANCE.CarToCarDTO(car);getMapper會去load接口的Impl後綴的實現類。
通過生成spring bean注入使用,Mapper註解加上spring配置,會自動生成一個bean,直接使用bean注入即可訪問。@Mapper(componentModel = "spring") public interface CarMapper { @Mapping(source = "numberOfSeats", target = "seatCount") CarDTO CarToCarDTO(Car car); }如果配置了spring bean訪問會在註解上自動加上@Component。如果是雙向映射,例如 從DO到DTO以及從DTO到DO,正向方法和反向方法的映射規則通常是相似的,並且可以通過切換源和目標來簡單地逆轉。使用註解@InheritInverseConfiguration 指示方法應繼承相應反向方法的反向配置。@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount") CarDTO CarToCarDTO(Car car);
@InheritInverseConfiguration Car CarDTOToCar(CarDTO carDTO); }有些情況下不需要映射轉換產生新的bean,而是更新已有的bean。@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount") void updateDTOFromCar(Car car, @MappingTarget CarDTO carDTO);集合類型(List,Set,Map等)的映射以與映射bean類型相同的方式完成,即通過在映射器接口中定義具有所需源類型和目標類型的映射方法。MapStruct支持Java Collection Framework中的多種可迭代類型。生成的代碼將包含一個循環,該循環遍歷源集合,轉換每個元素並將其放入目標集合。如果在給定的映射器或其使用的映射器中找到用於集合元素類型的映射方法,則將調用此方法以執行元素轉換,如果存在針對源元素類型和目標元素類型的隱式轉換,則將調用此轉換。@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount") CarDTO CarToCarDTO(Car car);
List<CarDTO> carsToCarDtos(List<Car> cars);
Set<String> integerSetToStringSet(Set<Integer> integers);
@MapMapping(valueDateFormat = "dd.MM.yyyy") Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source); }MapStruct 還支持具有多個源參數的映射方法。例如,將多個實體組合成一個數據傳輸對象。在原案例新增一個Person對象,CarDTO中新增driverName屬性,根據Person對象獲得。@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "car.numberOfSeats", target = "seatCount") @Mapping(source = "person.name", target = "driverName") CarDTO CarToCarDTO(Car car, Person person); }編譯生成的代碼:
如果相應的源屬性是null ,則可以指定默認值以將預定義值設置為目標屬性。在任何情況下,都可以指定常量來設置這樣的預定義值。默認值和常量被指定為字符串值。當目標類型是原始類型或裝箱類型時,String 值將採用字面量,在這種情況下允許位/八進位/十進位/十六進位模式,只要它們是有效的文字即可。在所有其他情況下,常量或默認值會通過內置轉換或調用其他映射方法進行類型轉換,以匹配目標屬性所需的類型。@Mapper public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined") @Mapping(target = "longProperty", source = "longProp", defaultValue = "-1") @Mapping(target = "stringConstant", constant = "Constant Value") @Mapping(target = "integerConstant", constant = "14") @Mapping(target = "longWrapperConstant", constant = "3001") @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014") @Mapping(target = "stringListConstants", constant = "jack-jill-tom") Target sourceToTarget(Source s); }在某些情況下,可能需要手動實現 MapStruct 無法生成的從一種類型到另一種類型的特定映射。可以在Mapper中定義默認實現方法,生成轉換代碼將調用相關方法:
@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount") @Mapping(source = "length", target = "lengthType") CarDTO CarToCarDTO(Car car);
default String getLengthType(int length) { if (length > 5) { return "large"; } else { return "small"; } } }也可以定義其他映射器,如下案例Car中Date需要轉換成DTO中的String:public class DateMapper { public String asString(Date date) { return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ).format( date ) : null; }
public Date asDate(String date) { try { return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ).parse( date ) : null; } catch ( ParseException e ) { throw new RuntimeException( e ); } } }@Mapper(uses = DateMapper.class) public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount") CarDTO CarToCarDTO(Car car); }若遇到多個類似的方法調用時會出現模稜兩可,需使用@qualifiedBy指定:@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount") @Mapping(source = "length", target = "lengthType", qualifiedByName = "newStandard") CarDTO CarToCarDTO(Car car);
@Named("oldStandard") default String getLengthType(int length) { if (length > 5) { return "large"; } else { return "small"; } } @Named("newStandard") default String getLengthType2(int length) { if (length > 7) { return "large"; } else { return "small"; } } }
表達式自定義映射
目前僅支持 Java 作為語言。例如,此功能可用於調用構造函數,整個源對象都可以在表達式中使用。應注意僅插入有效的 Java 代碼:MapStruct 不會在生成時驗證表達式,但在編譯期間生成的類中會顯示錯誤。@Data @AllArgsConstructor public class Driver { private String name; private int age; }@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "car.numberOfSeats", target = "seatCount") @Mapping(target = "driver", expression = "java( new com.alibaba.my.mapstruct.example4.beans.Driver(person.getName(), person.getAge()))") CarDTO CarToCarDTO(Car car, Person person); }@Mapper( imports = UUID.class )public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )") Target sourceToTarget(Source s); }在某些情況下,可能需要自定義生成的映射方法,例如在目標對象中設置無法由生成的方法實現設置的附加屬性。實現起來也很簡單,用裝飾器模式實現映射器的一個抽象類,在映射器Mapper中添加註解@DecoratedWith指向裝飾器類,使用時還是正常調用。@Mapper @DecoratedWith(CarMapperDecorator.class) public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount") CarDTO CarToCarDTO(Car car); }public abstract class CarMapperDecorator implements CarMapper { private final CarMapper delegate; protected CarMapperDecorator(CarMapper delegate) { this.delegate = delegate; } @Override public CarDTO CarToCarDTO(Car car) { CarDTO dto = delegate.CarToCarDTO(car); dto.setMakeInfo(car.getMake() + " " + new SimpleDateFormat( "yyyy-MM-dd" ).format(car.getCreateDate())); return dto; } }技術公開課
Java高級編程
本課程共162課時,包含Java多線程編程、常用類庫、IO編程、網絡編程、類集框架、JDBC等實用開發技術,幫助同學們掌握系統提供的類庫並熟練使用JavaDoc文檔。同時考慮到對面向對象的理解以及常用類的設計模式,在課程講解中還將進行原始碼的使用分析與結構分析。