我需要在生成的映射器实现中注入一个 spring 服务类,这样我就可以通过
使用它 @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() ) );
}
}
服务可以作为上下文传递。
更好的解决方案是使用包装
@Context
的MyService
类,而不是直接传递MyService
。 @AfterMapping
方法可以在这个“上下文”类上实现: void map( @MappingTarget Target.X target, Source.ID source )
保持映射逻辑与查找逻辑清晰。在 MapStruct example repository. 中查看此示例
除了上述答案之外,值得补充的是,在 mapstruct 映射器中有更干净的方式使用 spring 服务,更符合“关注点分离”的设计理念,称为“限定符”。作为奖励,在其他映射器中易于重用。 为了简单起见,我更喜欢这里提到的命名限定符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);
}
}
这就是您在映射器中使用它的方式:
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,我发现使用 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。
我不能使用
componentModel="spring"
,因为我在一个不使用它的大型项目中工作。许多映射器包括我的带有Mappers.getMapper(FamilyBasePersonMapper.class)
的映射器,这个实例不是Spring bean并且我的映射器中的@Autowired
字段为空。
我无法修改所有使用我的映射器的映射器。而且我不能将特定的构造函数与注入或 Spring 的
@Autowired
依赖注入一起使用。
我找到的解决方案:不直接使用 Spring 而使用 Spring bean 实例:
这是第一个注册自己的 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 的 Mapper
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);
//...
也许这不是最好的解决方案。但这有助于减少最终决议变化的影响。
我浏览了这个问题中的所有答案,但无法正常工作。我进一步挖掘并能够很容易地解决这个问题。您需要做的就是确保:
@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(); } }
另外,请记住,您的映射器现在是一个 spring bean。您需要将其注入您的调用类,如下所示:
private final MyMapper myMapper;
从 mapstruct 1.5.0 开始,您可以使用常量生成弹簧组件模型
@Mapper(
uses = {
//Other mappings..
},
componentModel = MappingConstants.ComponentModel.SPRING)