【问题标题】:How to map Collections in Mapstruct when generic type has Inheritance structure?当泛型类型具有继承结构时,如何在 Mapstruct 中映射集合?
【发布时间】:2019-04-02 16:31:17
【问题描述】:

我的项目中有以下 bean 结构。

 public class Account{
       // properties
       // setters
       // getters
    }

   public class AccountType1 extends Acccount{
       // properties
       // setters
       // getters
   }

    public class AccountType3 extends Acccount{
       // properties
       // setters
       // getters
   }

   public class CustomerProfile {
       Customer customer;
       List<Account> accounts;
       List<Service> services;

   }

我对客户和服务有类似的结构。一个父级和多个实现。我的应用程序是一个中间件应用程序。我不知道我们的应用程序从其他 Web 服务调用中获得了什么样的运行时对象(Bean 模型在应用程序中是相同的)。 List 可以包含任何实现。它可以是 Account 或 AccountType1 或 AccountType2。服务也是如此。父级将具有公共字段,每个实现将具有特定字段。我们将为每个新客户(即消费者)提供一个新流程。场地要求也不同。所以我们需要有单独的 CustomerProfile 和对应的 Account 和 Service 映射器。现在对于 client1,他们可能需要通用 Account 或 AccountType1 或 AccountType2 或 AccountTypeN 或所有这些。所以代码应该是通用的,就像我在配置中给 {AccountType1.class, AccounTypeN.class} 的任何类型的类一样,它应该只从列表中映射这些对象。由于 AccountType1 扩展了 Account,它还应该处理父类字段。我目前正在这样做。

    @Mapper(config = GlobalConfig.class)
    public interface CustomerProfileMapper{

    @Mappings({
        @Mapping( target = "customer", source = "customer"),
        @Mapping( target = "accounts", source = "accounts"),
        @Mapping( target = "services", source = "services")
    })
    CustomerProfile mapCustomerProfile(CustomerProfile customerProfile);

    @IterableMapping(qualifiedByName = "mapAccount")
    List<Account> mapAccounts(List<Account> accounts);

    @Named("mapAccount")
    default Account mapAccount (Account account){

       if(account instanceof AccountType1){
            mapAccountType1((AccountType1)account);
        }
       else if(account instanceof AccountType2){
            mapAccountType2((AccountType2)account);
        }
       else {
            mapBaseAccount(account);
        }
    }

   @Mappings{...}
   AccountType1 mapAccountType1(AccountType1  account);

   @Mappings{...}
   AccountType2 mapAccountType2(AccountType2  account);
   }

   @Mappings{...}
   Account mapBaseAccount(Account  account);

}

但是此代码将是多余的,因为我必须为不同的 CustomerProfileMappers 的每个流编写。我希望代码是通用的并且可以用作配置。可重用性是这里的关注点。如何解决这个问题?基本上我想做类似下面的事情。

@IterableMapping( mappingClasses= {AccountType1.class, AccountType2.class, Account.class})
    List<Account> mapAccounts(List<Account> accounts);
     @Mappings{...}
       AccountType1 mapAccountType1(AccountType1  account);

       @Mappings{...}
       AccountType2 mapAccountType2(AccountType2  account);
       }

       @Mappings{...}
       Account mapBaseAccount(Account  account);

所以 mapStruct 应该像我目前处理的那样生成代码。它应该生成映射方法来处理在 mappingClasses 属性中定义的所有指定类。它还应该在映射器中寻找各个类特定的映射方法。如果找到调用它们或者生成映射方法。这是必需的,因为我与客户和服务有类似的事情。我不想在映射器中编写太多手写代码,并且每个流程都有数十个不同的 CustomerProfileMapper。并且随着每个版本的发布而不断增加。我已经阅读了 MapStruct 的完整技术文档。但我找不到办法做到这一点。或者这可能是一个新的 FR?

【问题讨论】:

  • 如果你想在你的模块中映射 ypur 对象,你是所有的扩展吗?
  • @Filip 没有收到你的问题。
  • 您说您正在构建一个由多个应用程序使用的中间件 abd。是您一方的一个父级的实现还是其他应用程序可以扩展您的父级?
  • 我们使用的 Bean 模型在所有客户端(即生产者和消费者)中都是通用的。我的意思是我所说的用例将在我们的应用程序中使用。
  • 我更新了描述。请再过一遍。

标签: java java-8 mapstruct


【解决方案1】:

您可以尝试将启用帐户的开关外部化。

@Mapper(config = GlobalConfig.class)
public interface CustomerProfileMapper{

    @Mappings({
        @Mapping( target = "customer", source = "customer"),
        @Mapping( target = "accounts", source = "accounts"),
        @Mapping( target = "services", source = "services")
    })
    CustomerProfile mapCustomerProfile(CustomerProfile customerProfile, @Context MyMappingContext ctx);

   @Mappings{...}
   AccountType1 mapAccountType1(AccountType1  account);

   @Mappings{...}
   AccountType2 mapAccountType2(AccountType2  account);
   }

   @Mappings{...}
   Account mapBaseAccount(Account  account);
}

public class MyMappingContext {

    // or whatever component model you prefer
    CustomerProfileMapper mapper = CustomerProfileMapper.INSTANCE;

    @AfterMaping
    public void mapAccount (CustomerProfile source, @MappingTarget CustomerProfile target){

       for ( Account account : source.getAccounts() ){ 

       if(account instanceof AccountType1){
            mapper.mapAccountType1((AccountType1)account);
        }
       else if(account instanceof AccountType2){
            mapper.mapAccountType2((AccountType2)account);
        }
       else {
            mapper.mapBaseAccount(account);
        }
    }
   }
}

你甚至可以从这个上下文回调到映射器。如果您愿意,您可以将映射器本身的通用行为表达为通用映射器。因此,在 CommonCustomerProfileMapper 中定义签名并让 CustomerProfileMapper1 从该签名继承并定义映射。

写到 MapStruct 中的一个新特性:就像说的那样:我不确定这样一个特性有多少共同兴趣,但你总是可以发出一个特性请求。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-12
    • 1970-01-01
    • 2021-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多