Проблема зависимости автоматического цикла Springboot и Mapstruct

У меня есть приложение 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, чтобы избежать цикла.

Мне кажется, что проблема в большей степени связана с тем, что картографы подключаются автоматически. а не контексты, используемые в самих методах, которые вызывают эту ошибку циклической зависимости.

В результате моего исследования было найдено несколько обходных путей, которые мне не особенно понравились.

  1. Аннотируйте экземпляры автоматически подключенных картографов с помощью @Lazy, что работает, однако для этого требуется, чтобы я пошел и вручную добавил @Lazy ко всем экземплярам картографов, которые находятся в циклах в их соответствующих реализациях картографов - этот метод отстой, потому что после любого изменения или перезагрузки Mapstruct реконструирует код и, таким образом, стирает мои аннотации, добавленные вручную @lazy, заставляют меня снова и снова выполнять одну и ту же ненужную работу.

  2. генеративный искусственный интеллект предполагает, что я могу использовать автоматическое связывание с конструкторами, а не автоматическое связывание с переменными поля. Мне не нравится этот обходной путь, потому что он кажется ненужным и не сработает.

  3. Я всегда могу удалить работу, которую выполняет Springboot, и реализовать свои картографы и их bean-компоненты вручную, как показано на странице github, однако мне не нравится тот факт, что у меня есть доступ к Springboot и я не могу использовать его потрясающие возможности вместе с Mapstruct просто потому что автоматическое подключение вызывает циклические зависимости без другого документированного решения.

0
0
205
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете использовать инъекцию сеттера, доступную в версии 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.

Другие вопросы по теме