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

nwlqm0z1  于 2023-03-16  发布在  Spring
关注(0)|答案(7)|浏览(270)

我需要在生成的Map器实现中注入一个spring服务类,以便可以通过

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

这是否适用于Mapstruct-1.0?

lfapxunr

lfapxunr1#

正如brettanomyces所评论的,如果服务不用于表达式以外的Map操作,它就不会被注入。
我发现的唯一办法是:

  • 将Map器接口转换为抽象类
  • 在抽象类中注入服务
  • 使其受保护,以便抽象类的“实现”具有访问权限

我使用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);

}
oogrdqng

oogrdqng2#

如果您将Spring声明为组件模型,并添加对myservice类型的引用,则应该可以实现这一点:

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

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

mepcadol

mepcadol3#

从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类来 Package MyService,而不是直接传递MyService。一个@AfterMapping方法可以在这个“context”类上实现:void map( @MappingTarget Target.X target, Source.ID source )保持Map逻辑不受查找逻辑影响。请在MapStruct example repository中查看此示例。

brqmpdu1

brqmpdu14#

除了上面的答案之外,值得补充的是在mapstructMap器中使用spring服务有更简洁的方法,它更适合“关注点分离”的设计概念,称为“qualifier”。在其他Map器中易于重用是一个额外的好处。为了简单起见,我更喜欢命名的qualifier,如www.example.com所示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);
    }

}

以下是在Map器中使用它的方式:

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);

}
dluptydi

dluptydi5#

我使用的是Mapstruct 1.3.1,我发现这个问题很容易用装饰器来解决。
示例:

@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。

0g0grzrc

0g0grzrc6#

我不能使用componentModel="spring",因为我在一个大项目中工作,而这个项目没有使用componentModel="spring"。许多Map器都包含了我的Map器Mappers.getMapper(FamilyBasePersonMapper.class),这个示例不是Spring bean,而且我的Map器中的@Autowired字段为空。
我不能修改所有使用我的Map器的Map器。而且我不能使用特定的构造函数来注入或者Spring的@Autowired依赖注入。
我找到的解决方案是:使用Spring bean示例而不直接使用Spring:
下面是注册自身第一个示例(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 accross getFirstInstance方法的Map器:

@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);

   //...

也许这不是最好的解决办法,但它有助于减少最后决议案中的变化所产生的影响。

zysjyyx4

zysjyyx47#

我看了这个问题的所有答案,但还是没能找到答案。我进一步挖掘了一下,很容易就解决了这个问题。你需要做的就是确保:

  1. componentModel设置为“Spring”
    1.您正在为Map器使用抽象类。
    1.定义一个命名方法,您将在其中使用注入的bean(在本例中,它的***appProperties***将在***mapSource***方法中使用)
@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();
   } }

另外,记住,你的Map器现在是一个springbean,你需要把它注入到你的调用类中,如下所示:

private final MyMapper myMapper;

相关问题