web-dev-qa-db-fra.com

Mapstruct - Comment puis-je injecter une dépendance de ressort dans la classe Generated Mapper

J'ai besoin d'injecter une classe de service de ressort dans l'implémentation de mappeur générée, afin de pouvoir l'utiliser via 

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

Est-ce applicable dans Mapstruct-1.0?

7
Karim Tawfik

Cela devrait être possible si vous déclarez Spring en tant que modèle de composant et ajoutez une référence au type myservice:

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

Ce mécanisme est destiné à fournir un accès à d'autres méthodes de mappage appelées par le code généré, mais vous devriez également pouvoir les utiliser dans l'expression. Assurez-vous simplement que vous utilisez le nom correct du champ généré avec la référence de service.

5
Gunnar

Comme l'a commenté brettanomyces, le service ne sera pas injecté s'il n'est pas utilisé dans des opérations de mappage autres que des expressions.

Le seul moyen que j'ai trouvé pour cela est:

  • Transformer l'interface de mappeur en classe abstraite
  • Injecter le service dans la classe abstraite
  • Faites en sorte que la "mise en oeuvre" de la classe abstraite ait accès

J'utilise CDI mais ce devrait être le samel avec 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);

}
12
Bob

Depuis 1.2, cela peut être résolu avec une combinaison de @AfterMapping et @Context .. Comme ceci:

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

Le service peut être passé en tant que contexte. 

Une solution plus intéressante consisterait à utiliser une classe @Context qui encapsule MyService au lieu de passer directement MyService. Une méthode @AfterMapping peut être implémentée sur cette classe "context": void map( @MappingTarget Target.X target, Source.ID source ) en gardant la logique de mappage à l'écart de la logique de recherche. Consultez cet exemple dans le référentiel MapStruct example .

5
sjaak

Outre les réponses ci-dessus, il est intéressant d’ajouter qu’il existe un moyen plus propre d’utiliser le service Spring dans Mapstruct Mapper, qui correspond davantage au concept de conception "de séparation des préoccupations", appelé "qualificateur". simplicité je préfère le qualificatif nommé, comme indiqué ici http://mapstruct.org/documentation/stable/reference/html/#selection-based-on-qualifiers Exemple:

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

}

Voici comment vous l'utilisez dans votre mappeur:

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
Cmyker