Sinh mapper tự động
govex-cloud-mapstruct-processor là annotation processor chạy tại compile-time: đọc các annotation @AutoMapper, @AutoMappers, @AutoMapMapper, @AutoEnumMapper và sinh interface mapper, adapter cùng interface cấu hình MapStruct. Thêm vào build của service khi dùng các annotation của govex-cloud-mapstruct.
Khi nào sử dụng
- Service dùng
@AutoMapper/@AutoMapMapper/@AutoEnumMappervà cần mapper được sinh tự động khi biên dịch. - Cần đổi quy tắc sinh mapper (package, tên adapter, policy báo lỗi thuộc tính chưa map...) bằng compiler option.
- Cần trộn mapper sinh tự động với mapper
@Mapperviết tay trong cùng một module.
Cài đặt
Khai báo dependency (không khai version — version do BOM quản lý, xem Cài đặt):
<dependency>
<groupId>vn.govex.cloud</groupId>
<artifactId>govex-cloud-mapstruct-processor</artifactId>
<scope>provided</scope>
</dependency>
Điều kiện tiên quyết: govex-cloud-parent đã khai báo sẵn danh sách annotationProcessorPaths (lombok, spring-boot-configuration-processor). Khi annotationProcessorPaths được cấu hình, javac chỉ chạy processor trong danh sách đó — vì vậy bắt buộc thêm processor vào annotationProcessorPaths của maven-compiler-plugin, dependency thường không đủ:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<!-- giữ các path có sẵn từ parent (lombok, spring-boot-configuration-processor) -->
<path>
<groupId>vn.govex.cloud</groupId>
<artifactId>govex-cloud-mapstruct-processor</artifactId>
<version>${govex-cloud.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
annotationProcessorPaths không tự lấy version từ BOM; khai version khớp BOM đang dùng bằng property trong pom.xml (ví dụ govex-cloud.version).
Cấu hình
Toàn bộ cấu hình là compiler argument truyền qua maven-compiler-plugin dạng -A<key>=<value>:
| Option | Mô tả | Mặc định |
|---|---|---|
govex.mapstruct.mapperConfigClass | FQN class cấu hình MapStruct thay thế | rỗng |
govex.mapstruct.mapperPackage | Package chứa mapper sinh ra | package của lớp nguồn |
mapstruct.defaultComponentModel | Component model của mapper (MapStruct đọc) | spring-lazy |
govex.mapstruct.suppressTimestampInGenerated | Bỏ timestamp trong @Generated | rỗng |
govex.mapstruct.unmappedSourcePolicy, govex.mapstruct.unmappedTargetPolicy, govex.mapstruct.typeConversionPolicy | Chính sách báo cáo thuộc tính chưa map/chuyển kiểu | IGNORE |
govex.mapstruct.adapterPackage | Package chứa adapter sinh ra | vn.govex.cloud.mapstruct |
govex.mapstruct.adapterClassName | Tên adapter chuyển đổi | MapperConverterAdapter |
govex.mapstruct.autoConfigPackage | Package chứa class cấu hình tự động | vn.govex.cloud.mapstruct |
govex.mapstruct.autoMapperConfigClassName | Tên class cấu hình mapper | MapperAutoConfiguration |
govex.mapstruct.builder.buildMethod, govex.mapstruct.builder.disableBuilder | Tên method build và bật/tắt builder khi map | build, true |
Các chiến lược null/builder còn lại cũng theo cùng tiền tố govex.mapstruct.* và có thể truyền tương tự khi cần.
Sử dụng
Ví dụ mapper được sinh từ annotation:
@AutoMapper(target = UserVO.class, mapperNameSuffix = "System")
public class User { /* ... */ }
// → UserToUserVOMapperSystem implements GConverter<User, UserVO>
@AutoEnumMapper("code")
public enum Status { ACTIVE(1), INACTIVE(0) }
// → StatusMapper với hai method _toEnum/_toValue
Truyền compiler argument trong pom.xml:
<compilerArgs>
<arg>-Agovex.mapstruct.mapperPackage=vn.govex.app.mapper</arg>
<arg>-Amapstruct.suppressGeneratorTimestamp=true</arg>
<arg>-parameters</arg>
</compilerArgs>
Sau khi build, kiểm tra mã sinh ra trong target/generated-sources/annotations/ (IDE cần bật annotation processing để nhận diện).
Output sinh ra
Ví dụ minh hoạ output processor ghi vào target/generated-sources/annotations/ sau khi biên dịch. Lớp nguồn:
@AutoMapper(target = UserVO.class)
public class User {
@AutoMapping(target = "fullName")
private String name;
private Integer age;
}
UserToUserVOMapper.java — processor sinh interface mapper (tên {Nguồn}To{Đích}Mapper, kèm mapperNameSuffix nếu có); MapStruct đọc interface này để sinh implementation:
package vn.govex.app.entity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import vn.govex.cloud.mapstruct.GConverter;
import vn.govex.cloud.mapstruct.MapperAutoConfiguration;
@Mapper(
config = MapperAutoConfiguration.class,
uses = {},
imports = {})
public interface UserToUserVOMapper extends GConverter<User, UserVO> {
@Mapping(target = "fullName", source = "name")
UserVO convert(User source);
@Mapping(target = "fullName", source = "name")
UserVO convert(User source, @MappingTarget UserVO target);
}
Các policy còn lại (unmappedSourcePolicy, unmappedTargetPolicy, null handling, builder...) sinh thêm trong @Mapper theo annotation và compiler option.
UserToUserVOMapperImpl.java — MapStruct sinh implementation từ interface trên:
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
comments = "version: 1.6.x, compiler: javac, environment: Java 21")
@Component
public class UserToUserVOMapperImpl implements UserToUserVOMapper {
@Override
public UserVO convert(User source) {
if (source == null) {
return null;
}
UserVO target = new UserVO();
target.setFullName(source.getName());
target.setAge(source.getAge());
return target;
}
@Override
public UserVO convert(User source, UserVO target) {
if (source == null) {
return null;
}
target.setFullName(source.getName());
target.setAge(source.getAge());
return target;
}
}
MapperConverterAdapter.java — adapter @Component sinh một lần cho module (đổi qua govex.mapstruct.adapterClassName/adapterPackage):
package vn.govex.cloud.mapstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MapperConverterAdapter {
@Autowired
private GConversionService conversionService;
}
MapperAutoConfiguration.java — interface cấu hình MapStruct sinh một lần cho module (đổi qua govex.mapstruct.autoMapperConfigClassName/autoConfigPackage):
package vn.govex.cloud.mapstruct;
import org.mapstruct.Builder;
import org.mapstruct.MapperConfig;
import org.mapstruct.ReportingPolicy;
@MapperConfig(
componentModel = "spring-lazy",
uses = {MapperConverterAdapter.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE,
builder = @Builder(buildMethod = "build", disableBuilder = true))
public interface MapperAutoConfiguration {
}
Lưu ý
- File SPI của dependency đăng ký cả processor gốc
org.mapstruct.ap.MappingProcessor: không cần khai báo thêmorg.mapstruct:mapstruct-processorkhi đã dùng dependency này. - Annotation và API runtime (
GConverter,GConversionService) nằm ởgovex-cloud-mapstruct; processor chỉ chạy lúc biên dịch nên khaiscopelàprovided/không đóng gói vào artifact. - Mỗi module biên dịch chỉ được có một class
@AutoMapperConfig; hai mapper cùng cặp source-target sẽ gây lỗi biên dịch (DuplicateMapperException) hoặc processor tự thêm hậu tố__1,__2. - Adapter và class cấu hình mặc định sinh trong
vn.govex.cloud.mapstruct— phải khớp package được component-scan củaMapstructAutoConfigurationở runtime. - Sai cấu hình (target rỗng, trùng mapper...) làm hỏng biên dịch ngay với thông báo từ processor; xử lý lỗi biên dịch này thay vì thêm mapper thủ công.