需要对MapStruct有一定的了解。
通过一个简单的例子说明如何自定义映射规则。
后端有一张存储文件的数据表,数据表中存放的是文件在对象存储中的路径,在返回给前端的时候,我们希望能返回完整的URL,包含对象存储的endpoint和bucket。
例如source/image/001.png
,返回https://minio.example.com/bucket_static/source/image/001.png
。这样前端就可以直接通过此地址渲染页面。
下面说下怎么处理。
首先创建一个基本的映射
包含Entity、VO、Mapper,为了减少冗余代码,使用了Lombok,不喜欢的可自行生成Getter、Setter方法。
@Data
public class AttachmentEntity {
private Long id; // 主键
private String fileName; // 文件名
private Long fileSize; // 文件大小
private Date fileTime; // 文件时间
private String keyPath; // 对象存储地址
}
public class AttachmentVO {
private String fileName; // 文件名
private Long fileSize; // 文件大小
private Date fileTime; // 文件时间
private String keyPath; // 对象存储地址
}
@Mapper
public interface AttachmentConvert {
AttachmentConvert INSTANCE = Mappers.getMapper(AttachmentConvert.class);
AttachmentEntity convert(AttachmentVO vo);
AttachmentVO convert(AttachmentEntity entity);
List<AttachmentVO> convertList(List<AttachmentEntity> list);
}
通过方法名称选择映射规则
在AttachmentConvert中添加一个静态方法,方法入参为源字段值,返回值为目标字段值。通过@Named注解给这个方法加上一个标识。在需要执行转换的方法上,通过@Mapping注解指定要使用的映射方法。
@Mapper
public interface AttachmentConvert {
// snip
@Name("appendPrefix")
default static String appendPrefix(String keyPath) {
return String.join("/", MinioConstants.DOMAIN, MinioConstants.BUCKET, keyPath);
}
@Mapping(source = "keyPath", target = "keyPath", qualifiedByName = "appendPrefix")
AttachmentVO convert(AttachmentEntity entity);
}
interface MinioConstants {
String BUCKET = "bucket_static";
String DOMAIN = "https://minio.example.com";
}
通过注解方式选择映射规则
如果不止一个文件需要用到这种映射规则,那么每个Convert接口都写一个这样的映射方法,有点啰嗦,我们可以将此方法提取到外部,通过@Mapper注解的uses参数引入来调用。
像上面那样用@Named形式指定也是可以的,但是我们更加彻底一点,使用注解会更加优雅。
import org.mapstruct.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface MinioPrefixMapper {
}
public interface MinioPrefixMappers {
@MinioPrefixMapper
default static String appendPrefix(String keyPath) {
return String.join("/", MinioConstants.DOMAIN, MinioConstants.BUCKET, keyPath);
}
}
interface MinioConstants {
String BUCKET = "bucket_static";
String DOMAIN = "https://minio.example.com";
}
通过注解指定使用的映射方法。
@Mapper(uses = {MinioPrefixMappers.class})
public interface AttachmentConvert {
// snip
@Mapping(source = "keyPath", target = "keyPath", qualifiedBy = MinioPrefixMapper.class)
AttachmentVO convert(AttachmentEntity entity);
}
其他有趣的转换方法
自定义常量
@Mapper
public interface ExampleConvert {
// snip
@Mapping(target="enabled", constant="true")
ExampleVO convert(ExampleEntity entity);
}
表达式转换
@Mapper
public interface ExampleConvert {
// snip
@Mapping(target="createTime", dateFormat="yyyy-MM-dd HH:mm:ss", expression="java(new Date())")
ExampleVO convert(ExampleEntity entity);
}
自定义方法
对一些非常复杂的,可以编写default方法,自己实现转换过程。
@Mapper
public interface ExampleConvert {
// snip
default ExampleVO convert(ExampleEntity entity) {
if (entity == null) {
return null;
}
ExampleVO vo = new ExampleVO();
vo.setXX(entity.getXX);
// 复杂的转换逻辑
return vo;
}
}
非静态方法
在上述给对象存储添加前缀的方法使用了静态方法,这是因为如果不使用静态方法,MapStruct生成的代码中,每个Convert都会创建一个包含映射方法类的实例。
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-12-01T10:00:15+0800",
comments = "version: 1.5.3.Final, compiler: javac, environment: Java 21.0.1 (Eclipse Adoptium)"
)
public class AttachmentConvertImpl implements AttachmentConvert {
private final MinioPrefixMappers minioPrefixMappers = new MinioPrefixMappers();
// snip
}
如果映射方法无法使用静态方法,转换过程中存在运行期数据,例如在Spring项目中,通过ApplicationProperties读取对象存储的配置。
此时可以配置@Mapper注解的componentModel参数为“spring”,表示将从Spring容器中获取相关的Bean。需要将MinioPrefixMappers注入到Spring容器中。
@Component
public class MinioPrefixMappers {
@MinioPrefixMapper
public String appendPrefix(String keyPath) {
// snip
}
}
@Mapper(uses = {MinioPrefixMappers.class}, componentModel="spring")
public interface AttachmentConvert {
// snip
@Mapping(source = "keyPath", target = "keyPath", qualifiedBy = MinioPrefixMapper.class)
AttachmentVO convert(AttachmentEntity entity);
}