Mapstruct - 如何在 Generated Mapper 类中注入 spring 依赖项

问题描述 投票:0回答:8

我需要在生成的映射器实现中注入一个 spring 服务类,这样我就可以通过

使用它
   @Mapping(target="x", expression="java(myservice.findById(id))")"

这适用于 Mapstruct-1.0 吗?

spring mapstruct
8个回答
48
投票

正如 brettanomyces 评论的那样,如果该服务不用于表达式以外的映射操作,则不会被注入。

我发现的唯一方法是:

  • 将我的映射器接口转换为抽象类
  • 在抽象类中注入服务
  • 使其受到保护,以便抽象类的“实现”可以访问

我正在使用 CDI,但它应该与 Spring 相同:

@Mapper(
        unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE,
        componentModel = "spring",
        uses = {
            // My other mappers...
        })
public abstract class MyMapper {

    @Autowired
    protected MyService myService;

    @Mappings({
        @Mapping(target="x", expression="java(myservice.findById(obj.getId())))")
    })
    public abstract Dto myMappingMethod(Object obj);

}

36
投票

声明Spring为组件模型,加上对

myservice
类型的引用应该可以:

@Mapper(componentModel="spring", uses=MyService.class)
public interface MyMapper { ... }

该机制旨在提供对生成代码调用的其他映射方法的访问,但您也应该能够以这种方式在表达式中使用它们。只需确保在服务引用中使用生成字段的正确名称即可。


28
投票

从 1.2 开始,这可以通过 @AfterMapping 和 @Context 的组合来解决。像这样:

@Mapper(componentModel="spring")
public interface MyMapper { 

   @Mapping(target="x",ignore = true)
   // other mappings
   Target map( Source source, @Context MyService service);

   @AfterMapping
   default void map( @MappingTarget Target.X target, Source.ID source, @Context MyService service) {
        target.set( service.findById( source.getId() ) );
   }
 }

服务可以作为上下文传递。

更好的解决方案是使用包装

@Context
MyService
类,而不是直接传递
MyService
@AfterMapping
方法可以在这个“上下文”类上实现:
void map( @MappingTarget Target.X target, Source.ID source )
保持映射逻辑与查找逻辑清晰。在 MapStruct example repository.

中查看此示例

26
投票

除了上述答案之外,值得补充的是,在 mapstruct 映射器中有更干净的方式使用 spring 服务,更符合“关注点分离”的设计理念,称为“限定符”。作为奖励,在其他映射器中易于重用。 为了简单起见,我更喜欢这里提到的命名限定符http://mapstruct.org/documentation/stable/reference/html/#selection-based-on-qualifiers 例子是:

import org.mapstruct.Mapper;
import org.mapstruct.Named;
import org.springframework.stereotype.Component;

@Component
@Mapper
public class EventTimeQualifier {

    private EventTimeFactory eventTimeFactory; // ---> this is the service you want yo use

    public EventTimeQualifier(EventTimeFactory eventTimeFactory) {
        this.eventTimeFactory = eventTimeFactory;
    }

    @Named("stringToEventTime")
    public EventTime stringToEventTime(String time) {
        return eventTimeFactory.fromString(time);
    }

}

这就是您在映射器中使用它的方式:

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring", uses = EventTimeQualifier.class)
public interface EventMapper {

    @Mapping(source = "checkpointTime", target = "eventTime", qualifiedByName = "stringToEventTime")
    Event map(EventDTO eventDTO);

}

21
投票

我正在使用 Mapstruct 1.3.1,我发现使用 decorator 很容易解决这个问题。

例子:

@Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE,
 componentModel = "spring")
@DecoratedWith(FooMapperDecorator.class)
public interface FooMapper {

    FooDTO map(Foo foo);
}
public abstract class FooMapperDecorator implements FooMapper{

    @Autowired
    @Qualifier("delegate")
    private FooMapper delegate;

    @Autowired
    private MyBean myBean;

    @Override
    public FooDTO map(Foo foo) {

        FooDTO fooDTO = delegate.map(foo);

        fooDTO.setBar(myBean.getBar(foo.getBarId());

        return fooDTO;
    }
}

Mapstruct 将生成 2 个类并将扩展 FooMapperDecorator 的 FooMapper 标记为 @Primary bean。


0
投票

我不能使用

componentModel="spring"
,因为我在一个不使用它的大型项目中工作。许多映射器包括我的带有
Mappers.getMapper(FamilyBasePersonMapper.class)
的映射器,这个实例不是Spring bean并且我的映射器中的
@Autowired
字段为空。

我无法修改所有使用我的映射器的映射器。而且我不能将特定的构造函数与注入或 Spring 的

@Autowired
依赖注入一起使用。

我找到的解决方案:不直接使用 Spring 而使用 Spring bean 实例:

这是第一个注册自己的 Spring 组件(Spring 实例):

@Component
@Mapper
public class PermamentAddressMapper {
    @Autowired
    private TypeAddressRepository typeRepository;

    @Autowired
    private PersonAddressRepository personAddressRepository;

    static protected PermamentAddressMapper FIRST_INSTANCE;

    public PermamentAddressMapper() {
        if(FIRST_INSTANCE == null) {
            FIRST_INSTANCE = this;
        }
    }

    public static PermamentAddressMapper getFirstInstance(){
        return FIRST_INSTANCE;
    }

    public static AddressDTO idPersonToPermamentAddress(Integer idPerson) {
        //...
    }

    //...

}

这里是使用 Spring Bean 的 Mapper

getFirstInstance
方法:

@Mapper(uses = { NationalityMapper.class, CountryMapper.class, DocumentTypeMapper.class })
public interface FamilyBasePersonMapper {

    static FamilyBasePersonMapper INSTANCE = Mappers.getMapper(FamilyBasePersonMapper.class);

    @Named("idPersonToPermamentAddress")
    default AddressDTO idPersonToPermamentAddress(Integer idPerson) {
        return PermamentAddressMapper.getFirstInstance()
            .idPersonToPermamentAddress(idPersona);
    }

    @Mapping(
        source = "idPerson",
        target="permamentAddres", 
        qualifiedByName="idPersonToPermamentAddress" )
    @Mapping(
        source = "idPerson",
        target = "idPerson")
    FamilyDTO toFamily(PersonBase person);

   //...

也许这不是最好的解决方案。但这有助于减少最终决议变化的影响。


0
投票

我浏览了这个问题中的所有答案,但无法正常工作。我进一步挖掘并能够很容易地解决这个问题。您需要做的就是确保:

  1. 您的 componentModel 设置为“spring”
  2. 你正在为你的映射器使用一个抽象类。
  3. 定义一个命名方法,您将在其中使用注入的 bean(在示例中,它的 appPropertiesmapSource 方法中使用)
@Mapper(componentModel = "spring") 
public abstract class MyMapper {

   @Autowired
   protected AppProperties appProperties;

   @Mapping(target = "account", source = "request.account")
   @Mapping(target = "departmentId", source = "request.departmentId")
   @Mapping(target = "source", source = ".", qualifiedByName = "mapSource")
   public abstract MyDestinationClass getDestinationClass(MySourceClass request);

   @Named("mapSource")
   String mapSource(MySourceClass request) {
      return appProperties.getSource();
   } }

另外,请记住,您的映射器现在是一个 spring bean。您需要将其注入您的调用类,如下所示:

private final MyMapper myMapper;

0
投票

从 mapstruct 1.5.0 开始,您可以使用常量生成弹簧组件模型

@Mapper(
    uses = {
        //Other mappings..
    },
    componentModel = MappingConstants.ComponentModel.SPRING)
© www.soinside.com 2019 - 2024. All rights reserved.