我有一个 springboot 应用程序,它依赖于许多(mapstruct)映射器 bean。在许多情况下,一个映射器以双向方式依赖另一个映射器(从而形成一个循环)。
Spring 不喜欢这样并抱怨。
应用程序无法启动
描述:
应用程序上下文中的一些bean的依赖关系形成一个循环:
┌──────┐ | AssociatedAccountMapperImpl(字段私有 com.server.springboot.mapping.global.ContactLocationRelationMapper com.server.springboot.mapping.customers.AssociatedAccountMapperImpl.contactLocationRelationMapper) ↑ ↓ | contactLocationRelationMapperImpl (字段私有 com.server.springboot.mapping.customers.AssociatedAccountMapper com.server.springboot.mapping.global.ContactLocationRelationMapperImpl.linkedAccountMapper) └──────┘
行动:
不鼓励依赖循环引用,并且默认情况下禁止它们。更新您的应用程序以消除 Bean 之间的依赖循环。作为最后的手段,可以通过将 spring.main.allow-circular-references 设置为 true 来自动打破循环。
现在我已经按照官方的mapstruct指南text来解决周期性问题,但是我特定的springboot和mapstruct组合问题似乎在互联网上的任何地方都没有记录的答案。
您在他们的回答中看到,他们建议您将
CycleAvoidingMappingContext
对象作为参数传递到您的方法中,然后他们通过以下调用检索映射器 EmployeeMapper MAPPER = Mappers.getMapper( EmployeeMapper.class );
但是,在我使用 springboot 的情况下,我通过 '@Mapper(componentModel = "spring",uses = CycleAvoidingMappingContext.class)` 注释自动装配了
CycleAvoidingMappingContext
对象。
这是发布的 github 解决方案
public class CycleAvoidingMappingContext {
private Map<Object, Object> knownInstances = new IdentityHashMap<Object, Object>();
@BeforeMapping
public <T> T getMappedInstance(Object source, @TargetType Class<T> targetType) {
return (T) knownInstances.get( source );
}
@BeforeMapping
public void storeMappedInstance(Object source, @MappingTarget Object target) {
knownInstances.put( source, target );
}
}
这是接口及其关联的自动创建代码的示例。
@Mapper(
componentModel = "spring",
subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION,
uses = {
CycleAvoidingMappingContext.class,
CategoryMapper.class,
NoteMapper.class,
PaymentTermMapper.class,
PriceLevelMapper.class,
LateFeeMapper.class,
ContactLocationRelationMapper.class
}
)
public interface AssociatedAccountMapper {
AssociatedAccountDTO toDTO(AssociatedAccount source);
}
@Component
public class AssociatedAccountMapperImpl implements AssociatedAccountMapper {
@Autowired
private CycleAvoidingMappingContext cycleAvoidingMappingContext;
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private NoteMapper noteMapper;
@Autowired
private PaymentTermMapper paymentTermMapper;
@Autowired
private PriceLevelMapper priceLevelMapper;
@Autowired
private LateFeeMapper lateFeeMapper;
@Autowired
private ContactLocationRelationMapper contactLocationRelationMapper;
@Override
public AssociatedAccountDTO toDTO(AssociatedAccount source) {
AssociatedAccountDTO target = cycleAvoidingMappingContext.getMappedInstance( source, AssociatedAccountDTO.class );
正如您所看到的
uses =
注释中的 @Mapper
属性会导致所有类都被自动装配并在必要时由 mapstruct 使用。
这太棒了,但是正如您所看到的,这个类是与我的 contactLocationRelationMapper 自动装配的,而我的 contactLocationRelationMapper 又是与 AssosciatedAccountMapper 自动装配的,从而导致了这种循环依赖问题,尽管该方法使用
CycleAvoidingMappingContext
对象来避免循环.
所以在我看来,问题更多地来自于映射器被自动连接的事实 而不是方法本身使用的上下文导致了这种循环依赖错误。
根据我的研究,我发现有一些我并不是特别喜欢的解决方法。
用
@Lazy
注释自动装配的映射器实例,它可以工作,但是它需要我手动将 @Lazy
添加到各自映射器实现中循环的所有映射器实例中 - 这种方法很糟糕,因为在任何更改或重新加载之后,mapstruct重建代码,从而删除我手动添加的 @lazy
注释,让我一遍又一遍地做同样不必要的工作。
生成式人工智能建议我可以使用构造函数自动装配,而不是使用字段变量自动装配。我不喜欢这种解决办法,因为它似乎没有必要,而且不起作用。
我总是可以删除 springboot 正在做的工作并手动实现我的映射器及其 bean,如 github 页面所示,但是我不喜欢这样的事实:我可以访问 springboot 并且无法使用它的强大功能mapstruct 只是因为自动装配会导致循环依赖,而没有其他记录的解决方案。
您可以使用版本
1.6.0.Beta1
中提供的 setter 注入,Spring Docs 也建议这样做,如下所示:
@Mapper(
componentModel = "spring",
subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION,
injectionStrategy = InjectionStrategy.SETTER
uses = {
CycleAvoidingMappingContext.class,
CategoryMapper.class,
NoteMapper.class,
PaymentTermMapper.class,
PriceLevelMapper.class,
LateFeeMapper.class,
ContactLocationRelationMapper.class
}
)
public interface AssociatedAccountMapper {
AssociatedAccountDTO toDTO(AssociatedAccount source);
}
还有一个非官方的
springlazy
组件模型扩展。有关更多详细信息,请参阅 MapStruct GitHub 问题 #3558