События Spring Data MongoDB Lifecycle: всегда ли можно обрабатывать их синхронно? - PullRequest
2 голосов
/ 31 мая 2019

Я пытаюсь использовать события жизненного цикла Spring Data MongoDB для вычисления поля «после сохранения» на основе идентификатора, который был сгенерирован MongoDB при вставке. Справочная документация говорит следующее о другом событии, 'перед сохранением':

Чтобы перехватить объект перед его поступлением в базу данных, вы можете зарегистрировать подкласс org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener, который переопределяет метод onBeforeSave.Когда событие отправляется, ваш слушатель вызывается и передает объект домена и преобразованный com.mongodb.Document.В следующем примере показано, как это сделать:

Затем они предоставляют пример.

Я сделал нечто подобное для 'после сохранения':

public class MyMongoDbLifecycleListener extends AbstractMongoEventListener<MyModel> {
    @Override
    public void onAfterSave(AfterSaveEvent<MyModel> event) {
        super.onAfterSave(event);

        MyModel model = event.getSource();

        model.computeValueFromTheAssignedId();
    }
}

Этоработает в тестах, но в предпроизводственном тестировании мы натолкнулись на проблему: иногда (на самом деле, довольно часто) значение не вычисляется заранее до того момента, когда объект возвращается из метода Repository save().

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

<bean id="simpleAsyncTaskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor">
     ...
</bean>
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
    <property name="taskExecutor" ref="simpleAsyncTaskExecutor" />
    ...
</bean>

В SimpleApplicationEventMulticaster есть следующий код, который фактически выполняет многоадресную рассылку:

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        Executor executor = getTaskExecutor();
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

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

I 'Я в замешательстве.Формулировка документации («Перехватить объект перед ...») ясно указывает, что это происходит строго перед преобразованием / сохранением / чем-либо еще.Но простое (и естественное) изменение конфигурации нарушает эти гарантии.Я подозреваю, что я делаю что-то не так, но как мне сделать это правильно?

Единственная идея, которая у меня сейчас есть, - написать расширение мультикастера, которое бы специально обрабатывало все события, связанные с MongoDB, синхронно.Это должно работать, но должно ли быть так легко нарушить поведение системы?

1 Ответ

0 голосов
/ 09 июня 2019

Вот расширение SimpleApplicationEventMulticaster, которое позволяет достичь политики 'обработки синхронизации для событий spring-data-mongodb':

public class SystemEventsAreSyncronousMulticaster extends SimpleApplicationEventMulticaster {
    @Override
    public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor == null || eventMustBeProcessedSynchronously(event)) {
                invokeListener(listener, event);
            } else {
                executor.execute(() -> invokeListener(listener, event));
            }
        }
    }

    private boolean eventMustBeProcessedSynchronously(ApplicationEvent event) {
        return event instanceof MongoMappingEvent;
    }

    private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
        return ResolvableType.forInstance(event);
    }
}
...