programing

Mapstruct - Generated Mapper 클래스에서 스프링 종속성을 주입하는 방법

newnotes 2023. 9. 12. 20:42
반응형

Mapstruct - Generated Mapper 클래스에서 스프링 종속성을 주입하는 방법

생성된 맵퍼 구현에 스프링 서비스 클래스를 주입하여 다음을 통해 사용할 수 있도록 해야 합니다.

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

Mapstruct-1.0에서 적용 가능합니까?

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을 구성요소 모델로 선언하고 다음 유형에 대한 참조를 추가하면 가능합니다.myservice:

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

이 메커니즘은 생성된 코드에 의해 호출되는 다른 매핑 메서드에 대한 액세스를 제공하기 위한 것이지만, 표현식에서도 이 메서드를 사용할 수 있어야 합니다.서비스 참조와 함께 생성된 필드의 올바른 이름을 사용해야 합니다.

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

서비스를 컨텍스트로 전달할 수 있습니다.

더 좋은 해결책은 a를 사용하는 것일 것입니다.@Context랩을하는반MyService통과하지 않고MyService직접. 안@AfterMapping메소드는 다음과 같은 "method" 클래스에서 구현할 수 있습니다.void map( @MappingTarget Target.X target, Source.ID source )매핑 로직을 룩업 로직에서 삭제합니다.MapStruct 예제 저장소에서 이 예제를 확인합니다.

위의 답변 외에 추가할 가치가 있는 것은 지도 구조 지도 작성기에서 스프링 서비스를 사용할 수 있는 보다 깨끗한 방법이 있으며, 이는 "관심사 분리" 설계 개념인 "자격증"에 더 적합합니다.기타 맵퍼에서 손쉽게 재사용 가능한 것이 덤입니다.단순화를 위해 여기에 명시된 대로 명명된 한정자를 선호합니다. http://mapstruct.org/documentation/stable/reference/html/ #fault-based-on-qualifier의 예는 다음과 같습니다.

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

}

저는 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개의 클래스를 생성하고 FooMapper 데코레이터를 확장하는 FooMapper를 @Primary bean으로 표시합니다.

저는 이 질문에 대한 답변을 모두 검토했지만, 일이 제대로 진행되지 않았습니다.조금 더 파서 아주 쉽게 해결할 수 있었습니다.당신이 해야 할 일은 다음 사항을 확인할 수 있습니다.

  1. 구성 요소 모델이 "스프링"으로 설정되어 있습니다.
  2. 맵퍼에 추상 클래스를 사용하고 있습니다.
  3. 주입된 빈을 사용할 명명된 메서드를 정의합니다(예: 해당 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();
   } }

또한, 여러분의 지도는 이제 봄 콩이라는 것을 기억하세요.다음과 같이 호출 클래스에 주입해야 합니다.

private final MyMapper myMapper;

사용할 수 없습니다.componentModel="spring"사용하지 않는 대형 프로젝트에서 일하기 때문입니다.많은 맵퍼들이 제 맵퍼를 포함하고 있습니다.Mappers.getMapper(FamilyBasePersonMapper.class)과 , , 의 이 는 이 이 아닙니다.@Autowired내 맵퍼의 필드가 null입니다.

제 맵퍼를 사용하는 모든 맵퍼를 수정할 수는 없습니다.그리고 난 주사나 스프링스와 함께 특정한 컨스트럭터를 사용할 수 없습니다.@Autowired의존성 주사

찾은 해결책:Spring을 직접 사용하지 않고 Spring bean 인스턴스를 사용하는 경우:

다음은 첫 번째 인스턴스(Spring 인스턴스)를 등록하는 Spring Component입니다.

@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) {
        //...
    }

    //...

}

여기 스프링 빈 크로스를 사용하는 매퍼가 있습니다.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);

   //...

아마도 이것은 최선의 해결책이 아닐 것입니다.그러나 최종 해상도의 변화가 미치는 영향을 줄이는 데는 도움이 되었습니다.

mapstruct 1.5.0 이므로 spring component model 생성에 상수를 사용할 수 있습니다.

@Mapper(
    uses = {
        //Other mappings..
    },
    componentModel = MappingConstants.ComponentModel.SPRING)

언급URL : https://stackoverflow.com/questions/38807415/mapstruct-how-can-i-inject-a-spring-dependency-in-the-generated-mapper-class

반응형