Mapstruct - How can I inject a spring dependency in the Generated Mapper class

后端 未结 5 728
慢半拍i
慢半拍i 2020-12-05 17:52

I need to inject a spring service class in the generated mapper implementation, so that I can use it via

   @Mapping(target=\"x\", expression=\"java(myservi         


        
相关标签:
5条回答
  • 2020-12-05 18:29

    As commented by brettanomyces, the service won't be injected if it is not used in mapping operations other than expressions.

    The only way I found to this is :

    • Transform my mapper interface into an abstract class
    • Inject the service in the abstract class
    • Make it protected so the "implementation" of the abstract class has access

    I'm using CDI but it should be the samel with 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);
    
    }
    
    0 讨论(0)
  • 2020-12-05 18:29

    Since 1.2 this can be solved with a combination of @AfterMapping and @Context.. Like this:

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

    The service can be passed as context.

    A nicer solution would be to use an @Context class which wrap MyService in stead of passing MyService directly. An @AfterMapping method can be implemented on this "context" class: void map( @MappingTarget Target.X target, Source.ID source ) keeping the mapping logic clear of lookup logic. Checkout this example in the MapStruct example repository.

    0 讨论(0)
  • 2020-12-05 18:29

    What's worth to add in addition to the answers above is that there is more clean way to use spring service in mapstruct mapper, that fits more into "separation of concerns" design concept, called "qualifier". Easy re-usability in other mappers as a bonus. For sake of simplicity I prefer named qualifier as noted here http://mapstruct.org/documentation/stable/reference/html/#selection-based-on-qualifiers Example would be:

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

    This is how you use it in your mapper:

    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);
    
    }
    
    0 讨论(0)
  • 2020-12-05 18:32

    It should be possible if you declare Spring as the component model and add a reference to the type of myservice:

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

    That mechanism is meant for providing access to other mapping methods to be called by generated code, but you should be able to use them in the expression that way, too. Just make sure you use the correct name of the generated field with the service reference.

    0 讨论(0)
  • 2020-12-05 18:50

    I am using Mapstruct 1.3.1 and I have found this problem is easy to solve using a decorator.

    Example:

    @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 will generate 2 classes and mark the FooMapper that extends FooMapperDecorator as the @Primary bean.

    0 讨论(0)
提交回复
热议问题