自Java 16以降,Java Records已成定局;今Java 21已成长期支持版本,此物遍现——数据传输对象、值对象、领域模型皆是。其设计不可变,简练,且语义明晰。
然有隙焉,人皆不言:Java生态中诸般对象映射器,皆成于Records之先。者,乃循JavaBeans之制——可变之物,具取值、赋值之法,而无参构造之式。而Records则无此等物。然则何如?是故诸库皆以Records之部分支持为权宜之计,缝纫之迹昭然若揭。
吾乃建Immuto以补此缺憾。
后加Records之弊
一记录之身份,乃其正典构造也。
public record PersonDTO(Long id, String fullName, String email) {}
彼构造函数者,是也唯造之途也PersonDTO无设值者。无构建者,非自为之则无。组件访问者唯读不可写。
既存之制图者,未尝为此而设。欲与记录相协,则或:
- 生设法之呼而不存(遂于运行时败)
- 需尔自撰可变之构器以权宜
- 退而依隐私之域以映观——全然越乎正构之途
此皆运行时之败。非运行则不知其谬。
何以Immuto异于常者
Immuto乃注解处理器,其运行于mvn compile,若望Lombok与APT之法同工。其生平文也。.java调用汝记录之源文件正典构造函数直用无映像,无设置者,无运行时之意外。
@RecordMapper
public interface PersonMapper {
@Mapping(target = "fullName",
expression = "java(source.firstName() + \" \" + source.lastName())")
PersonDTO toDto(PersonEntity source);
@InheritInverseConfiguration(name = "toDto")
PersonEntity toEntity(PersonDTO source);
}
既mvn compile,Immuto乃书PersonMapperImpl.java于target/generated-sources。其形貌,恰似亲笔所撰之码:
@Generated("io.github.karunarathnad.immuto.processor.RecordMapperProcessor")
public final class PersonMapperImpl implements PersonMapper, ImmutoMapper {
@Override
public PersonDTO toDto(PersonEntity source) {
if (source == null) return null;
return new PersonDTO(
source.id(),
source.firstName() + " " + source.lastName(),
source.email()
);
}
}
此乃正宗之构,恒如是。此即Immuto所守之约也.
编译时之验
若记录组件不可映射,则构建失败—非运行时,非测试时,乃编译时也。
- 未映射之组件 → 构建之误
- 類型不協,無註冊轉換器 → 建構錯誤
-
@RecordMapper于类而非接口上构建 → 构建错误
此乃记录所宜之态。其设也,明且安;尔之映射器,亦当如是.
要点
嵌套记录 — 依构件名递归映射。用@Mapping(expression=...)以应非对称嵌套.
双向映射赖@InheritInverseConfiguration — 定toDto,得toEntity不取值。
@NullSafe— 以之裹其果Optional.ofNullable(...)于调用处:
@NullSafe
Optional<AddressDTO> toAddressDto(AddressEntity entity);
封类支持- 亘互通晓封域之序,此乃今世诸图匠所不能也。
生命循环钩子-@BeforeMapping且@AfterMapping之法,已内嵌于所生之码。无AOP,无代理。
定制之类型转换器:
@Named("isoDate")
public class IsoDateConverter implements TypeConverter<LocalDate, String> {
@Override
public String convert(LocalDate source, MappingContext ctx) {
return source == null ? null : source.toString();
}
}
流畅之运行时API:为测试或动态环境,APT不可用时:
FluentMapper<PersonEntity, PersonDTO> mapper = FluentMapper
.from(PersonEntity.class)
.to(PersonDTO.class)
.override("fullName", p -> p.firstName() + " " + p.lastName())
.build();
注:FluentMapper用反射——此乃显性选择之逃生通道,非默认路径也。
始之
<dependency>
<groupId>io.github.karunarathnad</groupId>
<artifactId>immuto-annotations</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>io.github.karunarathnad</groupId>
<artifactId>immuto-core</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>io.github.karunarathnad</groupId>
<artifactId>immuto-processor</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
于编译插件中添处理器路径
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.github.karunarathnad</groupId>
<artifactId>immuto-processor</artifactId>
<version>1.1.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
注释之界面,运行之mvn compile,且用之。
PersonMapper mapper = Immuto.getMapper(PersonMapper.class);
PersonDTO dto = mapper.toDto(entity);
何故于此?
Java 21乃今之长支持版本。记录非实验之物——乃现代Java中建模不可变数据之惯用之法。随更多代码库采用之,需治之工具亦随之增长,视之为首要公民(非边缘案例)。
Immuto 已登 Maven Central,依 Apache 2.0 许可,方在积极修整。
GitHub(IT之家)GitHub.com(karunarathnad/immuto)
惠赐评骘、陈弊、襄助,皆所欣然。












