Chuyển tới nội dung chính

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/@AutoEnumMapper và 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 @Mapper viế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>:

OptionMô tảMặc định
govex.mapstruct.mapperConfigClassFQN class cấu hình MapStruct thay thếrỗng
govex.mapstruct.mapperPackagePackage chứa mapper sinh rapackage của lớp nguồn
mapstruct.defaultComponentModelComponent model của mapper (MapStruct đọc)spring-lazy
govex.mapstruct.suppressTimestampInGeneratedBỏ timestamp trong @Generatedrỗng
govex.mapstruct.unmappedSourcePolicy, govex.mapstruct.unmappedTargetPolicy, govex.mapstruct.typeConversionPolicyChính sách báo cáo thuộc tính chưa map/chuyển kiểuIGNORE
govex.mapstruct.adapterPackagePackage chứa adapter sinh ravn.govex.cloud.mapstruct
govex.mapstruct.adapterClassNameTên adapter chuyển đổiMapperConverterAdapter
govex.mapstruct.autoConfigPackagePackage chứa class cấu hình tự độngvn.govex.cloud.mapstruct
govex.mapstruct.autoMapperConfigClassNameTên class cấu hình mapperMapperAutoConfiguration
govex.mapstruct.builder.buildMethod, govex.mapstruct.builder.disableBuilderTên method build và bật/tắt builder khi mapbuild, 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êm org.mapstruct:mapstruct-processor khi đã 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 khai scopeprovided/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ủa MapstructAutoConfiguration ở 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.