Перехват события ApplicationEvent

Добрый день!
Задача такая, есть некий сервис в котором есть метод sentMessage.
Метод sendMessage публикует событие MsgEvent через Events (в версии 6.7).
Нужно перехватить это событие в ExtAppMainWindow и вывести.

Проблема: я отправляю событие через Events, но в перехватчик в ExtAppMainWindow ничего не приходит.
Перехватывающий метод имеет аннотацию @EventListener

Если тип события MsgEvent наследовать от UiEvent, то его уже я не увижу в сервисе. А он мне нужен именно в сервисе…

События в UI полностью отделены от глобальных событий, вам потребуется самостоятельно регистрировать слушатели UI в глобальном компоненте-бине.

Например:

@Component
public class MessageEventBroadcaster {
    public final List<WeakReference<Consumer<GlobalMessageEvent>>> subscriptions = new ArrayList<>();

    @EventListener
    protected void onMessage(GlobalMessageEvent event) {
        synchronized (subscriptions) {
            Iterator<WeakReference<Consumer<GlobalMessageEvent>>> iterator = subscriptions.iterator();
            while (iterator.hasNext()) {
                WeakReference<Consumer<GlobalMessageEvent>> reference = iterator.next();
                Consumer<GlobalMessageEvent> eventConsumer = reference.get();
                if (eventConsumer == null) {
                    iterator.remove();
                } else {
                    eventConsumer.accept(event);
                }
            }
        }
    }

    public void subscribe(Consumer<GlobalMessageEvent> handler) {
        synchronized (subscriptions) {
            subscriptions.add(new WeakReference<>(handler));
        }
    }
}

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

В расширенном главном окне, мы подписываемся на рассылку события:

public class ExtAppMainWindow extends AppMainWindow {
    @Inject
    private MessageEventBroadcaster broadcaster;

    @Inject
    private BackgroundWorker backgroundWorker;

    private Consumer<GlobalMessageEvent> messageHandler;

    @Override
    public void init(Map<String, Object> params) {
        super.init(params);

        UIAccessor uiAccessor = backgroundWorker.getUIAccessor();
        // save reference to message handler
        messageHandler = event -> uiAccessor.access(() -> {
            showNotification(event.getMessage());
        });

        broadcaster.subscribe(messageHandler);
    }

Здесь мы создаём подписку на рассылку и обеспечиваем доступ к состоянию UI при помощи UIAccessor. Это необходимо, поскольку рассылка выполняется из другого потока. Из блока access { } мы можем изменять состояние UI, например, показывать нотификации.

Разослать событие можно при помощи бина Events:

    @Inject
    private Events events;

    public void onSendBtnClick() {
        events.publish(new GlobalMessageEvent("Demo for all!"));
    }

Я подготовил небольшой пример: https://github.com/cuba-labs/demo-broadcast-event

Обратите внимание, что в этом примере бин и экраны находятся в одном приложении - Web Client. Этот способ не подходит для отправки событий из core в Web Client.

2 симпатии

Отлично! Как можно отправить некое событие в виде Notification всем пользователям которые онлайн в системе или отправить только одному (по логину)

Я так понимаю, это уже через сокет решается?

Обработчик сработает у всех экземпляров ExtAppMainWindow, нужно будет в нём отфильтровать по текущему пользователю события, например добавив в сообщение поле “получатель”. Это всего лишь пример рассылки сообщений всем HTTP сессиям / открытым вкладкам браузера. Кроме того, здесь сейчас никак не обрабатываются ошибки, которые могут возникнуть.

Вызов ui.access() автоматически доставит изменения в браузер пользователя по WebSocket соединению, это не нужно реализовывать

Спасибо большое, все работает!

Можно ли как то событие перекинуть с core или global слоя на веб слой?

Пока “из коробки” такого способа нет. Есть тикет: https://youtrack.cuba-platform.com/issue/PL-9024

Подскажите пожалуйста, что то путаница в голове…
Если в ExtAppMainWindow создать метод

@EventListener
private void messgeHandle(GlobalMessageEvent e){}

То я ведь так же могу иметь доступ к ExtAppMainWindow и вызвать showNotification?

Есть ли нужда создавать MessageEventBroadcaster?

Да, вы можете в этом методе использовать showNotification, но глобальные события, опубликованные из бинов, не будут приходить в этот метод, пока вы сами их туда не отправите, как показано в примере с MessageEventBroadcaster.

1 симпатия

Если c какого либо контроллера страницы или с бина который находится в веб модуле, отправить событие GlobalMessageEvent, то все методы объявленные с аннотацией @EventListener(GlobalMessageEvent.class) в модуле web, получат это событие! Не так ведь? Объясните пожалуйста, запутался что то?) Или я что то не учитываю и ошибаюсь?

Объекты экранов UI не являются бинами и хранятся в пользовательских сессиях. Поэтому для обработчиков событий в них это не выполняется. События обрабатываются в экранах UI, только если порождены в экранах / в рамках пользовательской сессии и реализуют интерфейс UiEvent.

Ясно, спасибо! У меня получилось так, создал GlobalMessageEvent который наследует только ApplicationEvent. Написал @EventListener метод для GlobalMessageEvent в ExtAppMainWindow. И опубликовал это событие в TestEdit(стандартная форма редактирования сущности Test), и вуаля уведомление перехватил в ExtAppMainWindow. Это правильная реализация перехвата уведомлении?

Если вы опубликовали событие при помощи Events и оно не реализует интерфейс UiEvent, то ничего никуда прийти не должно было, только если вы не передали событие ещё как-то.

1 симпатия

Ясненько, спасибо большое! За оперативность +1