MapStruct无法映射自定义类型到Protobuf重复字段编译报错
MapStruct映射Protobuf重复字段的Qualifier编译错误解决
问题场景
使用MapStruct将Java对象的String字段映射到Protobuf的repeated string字段时,触发编译错误:
error: Qualifier error. No method found annotated with: [ @CustomConverter and @String2List ].
相关代码如下:
1. 自定义Qualifier注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.mapstruct.Qualifier; @Qualifier @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface CustomConverter { } @Qualifier @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface String2List { }
2. 转换器实现
@CustomConverter public class StringToListConverter { @String2List public List<String> splitDataToRoles(String data) { return data != null ? Arrays.stream(data.split(",")) .map(String::trim) .collect(Collectors.toList()) : Collections.emptyList(); } }
3. Java实体类
import lombok.Getter; import lombok.Setter; @Getter @Setter public class User { public String data; }
4. Protobuf定义
message UserProto { repeated string roles = 1; }
5. MapStruct Mapper
import org.mapstruct.CollectionMappingStrategy; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; @Mapper(uses = StringToListConverter.class, collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); @Mapping(source = "data", target = "roles", qualifiedBy = {CustomConverter.class, String2List.class}) UserProto toProto(User user); }
注:将目标改为包含List<String> roles的普通Java类UserV2时,映射可正常编译执行。
原因分析
问题核心在于集合映射策略与Qualifier匹配逻辑的冲突:
- 映射普通Java类的List字段时,MapStruct会直接寻找
String → List<String>的转换器,此时会合并识别类上的@CustomConverter和方法上的@String2List,成功匹配到转换方法。 - 映射Protobuf的repeated字段时,
ADDER_PREFERRED策略会让MapStruct优先调用Protobuf生成的addRoles(String)方法,此时它需要的是能将源对象转换为可迭代String集合的转换器,且Qualifier匹配仅检查方法本身的注解,不会合并类级别的注解,导致找不到同时标注两个Qualifier的方法。
解决方案
方案1:将类级Qualifier移至方法上
修改转换器,把@CustomConverter直接标注在转换方法上,让方法同时拥有两个Qualifier:
public class StringToListConverter { @CustomConverter @String2List public List<String> splitDataToRoles(String data) { return data != null ? Arrays.stream(data.split(",")) .map(String::trim) .collect(Collectors.toList()) : Collections.emptyList(); } }
方案2:切换集合映射策略为ACCESSOR_ONLY
若无需使用adder方法,可将Mapper的集合映射策略改为ACCESSOR_ONLY,让MapStruct直接调用Protobuf生成的setRoles(List<String>)方法,逻辑与普通Java类映射一致:
@Mapper(uses = StringToListConverter.class, collectionMappingStrategy = CollectionMappingStrategy.ACCESSOR_ONLY) public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); @Mapping(source = "data", target = "roles", qualifiedBy = {CustomConverter.class, String2List.class}) UserProto toProto(User user); }
方案3:新增适配Adder的转换方法
添加返回Iterable<String>的转换方法,并标注对应Qualifier,适配MapStruct的adder调用逻辑:
@CustomConverter public class StringToListConverter { @String2List public List<String> splitDataToRoles(String data) { return data != null ? Arrays.stream(data.split(",")) .map(String::trim) .collect(Collectors.toList()) : Collections.emptyList(); } @CustomConverter @String2List public Iterable<String> splitToIterable(String data) { return splitDataToRoles(data); } }
内容的提问来源于stack exchange,提问作者LoranceChen
相关产品推荐
相关产品推荐

