【问题标题】:Mapstruct - How can I inject a spring dependency in the Generated Mapper classMapstruct - 如何在 Generated Mapper 类中注入 spring 依赖项
【发布时间】:2016-12-12 22:37:04
【问题描述】:

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

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

这适用于 Mapstruct-1.0 吗?

【问题讨论】:

    标签: spring mapstruct


    【解决方案1】:

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

    import org.mapstruct.Named;
    import org.springframework.stereotype.Component;
    
    @Component
    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);
    
    }
    

    【讨论】:

    • 后映射中如何使用EventTimeQualifier?
    • Autowire 作为参数可能吗?
    【解决方案2】:

    我正在使用 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。

    【讨论】:

    • 这是我认为最好的答案
    • 这个实现似乎覆盖了每个字段的映射,我相信最初的问题是关于一个特定的字段。
    • @ManolisPap:不是真的,请仔细阅读@DecoratedWith 的文档。您注入生成的映射器( delegate)并首先应用它。然后,在装饰器方法主体中执行您的自定义。
    • 绝对可爱的解决方案,比任何其他解决方案都优雅。
    • 很棒的方法。工作完美。为了避免以下错误,我不得不将@Primary 添加到接口中:(someClass)中的字段映射器需要一个 bean,但找到了 2 个。
    【解决方案3】:

    正如 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);
    
    }
    

    【讨论】:

    • 我确认这个解决方案也适用于 Spring,使用 Mapstruct 1.1.0.Final
    • 谢谢。糟透了,您不能使用构造函数注入。
    • 在不加载 spring 上下文的情况下是否可以在单元测试中工作?
    • 如何使用此解决方案在构造函数中注入 MyService?
    • 到目前为止,这是最优雅的解决方案,感谢您的发帖!它可以扩展为以某种方式映射实体本身吗? IE。如果设置了 Dto.id 字段,它会从数据库中获取实体吗? @Mapping(target="this", expression="java(myotherservice.getById(obj.getId()))") 的排序。
    【解决方案4】:

    从 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() ) );
       }
     }
    

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

    更好的解决方案是使用包装MyService@Context 类,而不是直接传递MyService。可以在此“上下文”类上实现@AfterMapping 方法:void map( @MappingTarget Target.X target, Source.ID source ) 保持映射逻辑与查找逻辑无关。在 MapStruct example repository 中查看此示例。

    【讨论】:

    • 我会喜欢这个解决方案的。遗憾的是,这在使用构建器(版本 1.3.0.Beta2)时不起作用,因为需要设置器
    • 这是一个很好的建议。我试过了,它就像一个魅力。 map() 在构建器 build() 方法之前被调用。
    • 不适用于 mapstruct 1.3.1.Final。 '@AferMapping' 方法与 '@Context' MyMapper myMapper 已被忽略
    • 不适用于 mapstruct 1.4.1.Final。 '@AferMapping' 方法与 '@Context' MyMapper myMapper 已被忽略
    • 在不知道什么不起作用的情况下回复“它不起作用”有点困难。请查看链接中提供的示例。它在 1.4.1.Final 中仍然有效。如果 MapStruct 有问题,请提出问题。
    【解决方案5】:

    如果将Spring声明为组件模型并添加对myservice类型的引用应该可以:

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

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

    【讨论】:

    • 我有同样的问题,似乎只有当它们的方法之一用作源->目标映射时,使用“uses”声明的类才会自动装配,所以如果它们的唯一用途是他们将不会自动装配的表达式
    • 啊,如果您想在表达式中使用它,那将是一个有趣的点。你能在our tracker 中打开一个问题吗?谢谢!
    • for 'uses=' 通常添加其他映射器,我建议将接口更改为抽象类,正如 Bob 在下面回答的那样
    • 问题被创建为mapstruct#938。关闭时建议使用@Context 和手写方法。
    • 请注意:这个uses 对我有用,使用@Mapping(target="x", source="id") 之类的映射而不是@Mapping(target="x", expression="java(myservice.findById(id))")"
    猜你喜欢
    • 2021-02-22
    • 2014-09-06
    • 1970-01-01
    • 2011-06-12
    • 1970-01-01
    • 2019-06-17
    • 1970-01-01
    • 2016-02-24
    • 1970-01-01
    相关资源
    最近更新 更多