У меня есть приложение Springboot, которое использует несколько компонентов-сопоставителей (mapstruct). Во многих случаях один картограф полагается на другого в двунаправленном режиме (таким образом образуя цикл).
Весне это не нравится, и она жалуется.
ПРИЛОЖЕНИЕ НЕ ЗАПУСТИЛОСЬ
Описание:
Зависимости некоторых bean-компонентов в контексте приложения образуют цикл:
┌─────┐ | AssociateAccountMapperImpl (частное поле 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.associatedAccountMapper) └─────┘
Действие:
Полагаться на циклические ссылки не рекомендуется, и они запрещены по умолчанию. Обновите свое приложение, чтобы удалить цикл зависимостей между компонентами. В крайнем случае, можно автоматически разорвать цикл, установив для параметра Spring.main.allow-circular-reference значение true.
Теперь я следую официальному руководству по Mapstruct text для решения циклических проблем, однако моя конкретная проблема с комбинацией Springboot и Mapstruct, похоже, не имеет документированного ответа нигде в Интернете.
Вы видите, что в их ответе они предлагают вам передать объект CycleAvoidingMappingContext в ваши методы в качестве параметра, и они получают свои картографы с помощью следующего вызова EmployeeMapper MAPPER = Mappers.getMapper( EmployeeMapper.class );
Однако в моем случае с Springboot я автоматически подключил объект CycleAvoidingMappingContext через аннотацию @Mapper(comComponentModel = "spring", Uses = CycleAvoidingMappingContext.class)`.
Вот опубликованное решение 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, который, в свою очередь, автоматически связывается с AssosciatedAccountMapper, что приводит к этой проблеме циклических зависимостей, несмотря на то, что метод использует объект CycleAvoidingMappingContext, чтобы избежать цикла.
Мне кажется, что проблема в большей степени связана с тем, что картографы подключаются автоматически. а не контексты, используемые в самих методах, которые вызывают эту ошибку циклической зависимости.
В результате моего исследования было найдено несколько обходных путей, которые мне не особенно понравились.
Аннотируйте экземпляры автоматически подключенных картографов с помощью @Lazy, что работает, однако для этого требуется, чтобы я пошел и вручную добавил @Lazy ко всем экземплярам картографов, которые находятся в циклах в их соответствующих реализациях картографов - этот метод отстой, потому что после любого изменения или перезагрузки Mapstruct реконструирует код и, таким образом, стирает мои аннотации, добавленные вручную @lazy, заставляют меня снова и снова выполнять одну и ту же ненужную работу.
генеративный искусственный интеллект предполагает, что я могу использовать автоматическое связывание с конструкторами, а не автоматическое связывание с переменными поля. Мне не нравится этот обходной путь, потому что он кажется ненужным и не сработает.
Я всегда могу удалить работу, которую выполняет Springboot, и реализовать свои картографы и их bean-компоненты вручную, как показано на странице github, однако мне не нравится тот факт, что у меня есть доступ к Springboot и я не могу использовать его потрясающие возможности вместе с Mapstruct просто потому что автоматическое подключение вызывает циклические зависимости без другого документированного решения.
Вы можете использовать инъекцию сеттера, доступную в версии 1.6.0.Beta1, которая также предлагается в 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.