You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 11:42:03