Mapstruct - 如何在生成的映射器 class 中注入 spring 依赖项
Mapstruct - How can I inject a spring dependency in the Generated Mapper class
我需要在生成的映射器实现中注入一个 spring 服务 class,这样我就可以通过
使用它
@Mapping(target="x", expression="java(myservice.findById(id))")"
这适用于 Mapstruct-1.0 吗?
如果将Spring声明为组件模型并添加对myservice
类型的引用应该可以:
@Mapper(componentModel="spring", uses=MyService.class)
public interface MyMapper { ... }
该机制旨在提供对生成代码调用的其他映射方法的访问,但您也应该能够以这种方式在表达式中使用它们。只需确保将生成的字段名称与服务引用一起使用即可。
正如 brettanomyces 评论的那样,如果服务不用于表达式以外的映射操作,则不会注入该服务。
我发现的唯一方法是:
- 将我的映射器接口转换为抽象class
- 在抽象中注入服务class
- 使其受到保护,以便摘要 class 的 "implementation" 可以访问
我正在使用 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);
}
从 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
class 包装 MyService
而不是直接传递 MyService
。可以在此 "context" class 上实现 @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.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);
}
我正在使用 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。
我需要在生成的映射器实现中注入一个 spring 服务 class,这样我就可以通过
使用它 @Mapping(target="x", expression="java(myservice.findById(id))")"
这适用于 Mapstruct-1.0 吗?
如果将Spring声明为组件模型并添加对myservice
类型的引用应该可以:
@Mapper(componentModel="spring", uses=MyService.class)
public interface MyMapper { ... }
该机制旨在提供对生成代码调用的其他映射方法的访问,但您也应该能够以这种方式在表达式中使用它们。只需确保将生成的字段名称与服务引用一起使用即可。
正如 brettanomyces 评论的那样,如果服务不用于表达式以外的映射操作,则不会注入该服务。
我发现的唯一方法是:
- 将我的映射器接口转换为抽象class
- 在抽象中注入服务class
- 使其受到保护,以便摘要 class 的 "implementation" 可以访问
我正在使用 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);
}
从 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
class 包装 MyService
而不是直接传递 MyService
。可以在此 "context" class 上实现 @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.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);
}
我正在使用 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。