如何自定义MapStruct自动忽略Protobuf生成类的无关映射字段
MapStruct配合Protobuf映射冗余忽略问题解决方案
优先方案:官方Protobuf扩展+自定义访问者命名策略
该方案可以完全消除冗余的忽略注解,同时保留unmappedTargetPolicy=ERROR的严格校验规则,不需要调整现有全局配置。
- 引入MapStruct官方Protobuf扩展依赖,自动忽略Protobuf生成类的内置通用字段
扩展会自动识别Protobuf生成消息类中的内置方法与属性,包括mergeFrom、clearField、clearOneof、unknownFields、mergeUnknownFields、allFields,无需手动编写@Mapping(ignore = true)声明。Gradle依赖配置参考:
注意扩展版本需要和使用的MapStruct主版本保持兼容,大版本号一致即可。ext { mapstructVersion = "1.5.5.Final" mapstructProtobufExtensionVersion = "1.5.5" } dependencies { implementation "org.mapstruct:mapstruct:${mapstructVersion}" annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" annotationProcessor "org.mapstruct.extensions.protobuf:mapstruct-protobuf-extensions:${mapstructProtobufExtensionVersion}" } - 自定义访问者命名策略,自动过滤
*Bytes后缀字段
实现MapStruct开放的AccessorNamingStrategySPI,让MapStruct在扫描Protobuf类属性时,直接跳过所有xxxBytes格式的字段,不将其纳入映射校验范围。代码示例:package com.yourcompany.common.mapper; import com.google.protobuf.GeneratedMessageV3; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; public class ProtobufCompatibleNamingStrategy extends DefaultAccessorNamingStrategy { private TypeMirror protobufBaseType; @Override public void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); protobufBaseType = processingEnvironment.getElementUtils() .getTypeElement(GeneratedMessageV3.class.getCanonicalName()) .asType(); } @Override public boolean isGetterMethod(ExecutableElement method) { TypeElement enclosingClass = (TypeElement) method.getEnclosingElement(); // 仅对Protobuf生成类做特殊过滤 if (typeUtils.isSubtype(enclosingClass.asType(), protobufBaseType)) { String methodName = method.getSimpleName().toString(); // 跳过getXxxBytes格式的getter方法,对应xxx字段的Bytes格式属性 if (methodName.startsWith("get") && methodName.endsWith("Bytes")) { String propertyName = Strings.decapitalize(methodName.substring(3, methodName.length() - 5)); // 确认存在对应普通属性getter,避免误判自定义的Bytes字段 if (hasRegularGetter(enclosingClass, propertyName)) { return false; } } } return super.isGetterMethod(method); } private boolean hasRegularGetter(TypeElement classElement, String propertyName) { String regularGetterName = "get" + Strings.capitalize(propertyName); return classElement.getEnclosedElements().stream() .anyMatch(e -> e.getSimpleName().toString().equals(regularGetterName)); } } - 注册自定义命名策略,在现有Gradle编译参数中追加配置即可:
配置完成后,编写Protobuf映射时只需要声明业务字段的映射规则即可,不需要再写任何通用字段、Bytes字段的忽略注解,未映射的业务字段依然会正常触发编译报错。compileJava { options.compilerArgs += [ '-Amapstruct.unmappedTargetPolicy=ERROR', '-Amapstruct.accessorNamingStrategy=com.yourcompany.common.mapper.ProtobufCompatibleNamingStrategy' ] }
轻量替代方案:公共Mapper配置
如果不想引入额外扩展依赖、也不想自定义SPI,可以通过公共@MapperConfig把固定的通用字段忽略规则抽离,减少重复代码。这个方案无法自动处理动态的*Bytes后缀字段,只适合Protobuf字段数量少的场景。
- 定义全局Proto映射配置:
@MapperConfig( componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR ) @Mapping(target = "mergeFrom", ignore = true) @Mapping(target = "clearField", ignore = true) @Mapping(target = "clearOneof", ignore = true) @Mapping(target = "unknownFields", ignore = true) @Mapping(target = "mergeUnknownFields", ignore = true) @Mapping(target = "allFields", ignore = true) public interface GlobalProtoMapperConfig {} - 业务Mapper引用该配置,不需要重复编写通用字段的忽略注解:
@Mapper(config = GlobalProtoMapperConfig.class, uses = {...}) public interface ProtoMapperExample { // 仅需额外声明业务字段对应的*Bytes字段忽略规则,以及特殊字段映射 @Mapping(target = "aBytes", ignore = true) @Mapping(target = "bBytes", ignore = true) ProtoMessage toMessage(Source s); }
注意:如果使用MapStruct 1.4及以下版本,官方Protobuf扩展不兼容,请先升级MapStruct版本到1.5.x再使用上述方案。
内容的提问来源于stack exchange,提问作者Kalec
相关产品推荐
相关产品推荐

