Jmix 还有全局事件这个功能么

比如 两个 屏幕 的通信,一个改变另一个也改变,或者springbean 跟 屏幕 通信之类的

1 个赞

CUBA 中的全局事件是为了解决 web 和 core 模块之间通信的问题。现在 Jmix 不分这两个模块,也就没有这个功能了。

在 Jmix 中,从业务逻辑(bean)和持久层与 UI 进行通信是基于 Spring 的应用程序事件实现的解决方案。但是只能在单一服务端实例中使用。

一个简单的解决方案是使用框架提供的 UiEventPublisher bean。这个 bean 可以往当前用户打开的浏览器 tab 中发送事件。例如,如果用户在一个浏览器 tab 中点击某个按钮,按钮调用发送事件的代码,然后事件会发送至那个 tab 中的界面。

创建 Event 类:

package com.company.app.entity;

import org.springframework.context.ApplicationEvent;

public class FooChangedEvent extends ApplicationEvent {

    public FooChangedEvent(Object source) {
        super(source);
    }
}

在需要接收通知的界面内创建一个事件监听器。下面的代码是在主界面创建的:

public class MainScreen extends Screen implements Window.HasWorkArea {
// ...
    @Autowired
    private Notifications notifications;

    @EventListener
    private void fooChanged(FooChangedEvent event) {
        notifications.create().withCaption("A Foo instance has been changed").show();
    }

例如,当实体修改时,使用 UiEventPublisher 发送 event:

@Component
public class FooEventListener {

    @Autowired
    private UiEventPublisher uiEventPublisher;

    @TransactionalEventListener
    public void onFooChangedAfterCommit(EntityChangedEvent<Foo> event) {
        uiEventPublisher.publishEvent(new FooChangedEvent(this));
    }
}

复杂一点的情况,如果需要给当前所有用户的所有 tab 都发送 event,需要创建一个更加复杂的 publisher。下面是一个示例,这个 bean 可以注册服务端创建的所有 Vaadin 会话,对会话进行遍历并发送 event:

package com.company.app;

import com.vaadin.server.VaadinSession;
import io.jmix.ui.App;
import io.jmix.ui.AppUI;
import io.jmix.ui.event.AppInitializedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Component
public class GlobalUiEventPublisher {

    private static final Logger log = LoggerFactory.getLogger(GlobalUiEventPublisher.class);

    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    private final List<WeakReference<VaadinSession>> sessions = new ArrayList<>();

    @EventListener
    public void onAppStart(AppInitializedEvent event) {
        lock.writeLock().lock();
        try {
            sessions.add(new WeakReference<>(VaadinSession.getCurrent()));
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void publishEvent(ApplicationEvent event) {
        ArrayList<VaadinSession> activeSessions = new ArrayList<>();

        int removed = 0;
        lock.readLock().lock();
        try {
            for (Iterator<WeakReference<VaadinSession>> iterator = sessions.iterator(); iterator.hasNext(); ) {
                WeakReference<VaadinSession> reference = iterator.next();
                VaadinSession session = reference.get();
                if (session != null) {
                    activeSessions.add(session);
                } else {
                    lock.readLock().unlock();
                    lock.writeLock().lock();
                    try {
                        iterator.remove();
                        lock.readLock().lock();
                    } finally {
                        lock.writeLock().unlock();
                    }
                    removed++;
                }
            }
        } finally {
            lock.readLock().unlock();
        }

        if (removed > 0) {
            log.debug("Removed {} Vaadin sessions", removed);
        }
        log.debug("Sending {} to {} Vaadin sessions", event, activeSessions.size());

        for (VaadinSession session : activeSessions) {
            // obtain lock on session state
            session.access(() -> {
                if (session.getState() == VaadinSession.State.OPEN) {
                    // active app in this session
                    App app = App.getInstance();

                    // notify all opened web browser tabs
                    List<AppUI> appUIs = app.getAppUIs();
                    for (AppUI ui : appUIs) {
                        if (!ui.isClosing()) {
                            // work in context of UI
                            ui.accessSynchronously(() -> {
                                ui.getUiEventsMulticaster().multicastEvent(event);
                            });
                        }
                    }
                }
            });
        }
    }
}

下面是使用这个 publisher 的示例:

@Component
public class FooEventListener {

    @Autowired
    private GlobalUiEventPublisher globalUiEventPublisher;

    @TransactionalEventListener
    public void onFooChangedAfterCommit(EntityChangedEvent<Foo> event) {
        globalUiEventPublisher.publishEvent(new FooChangedEvent(this));
    }
}

好的,容我 这边试试,感觉应没啥问题,感谢解答(回复的有点晚见谅)

1 个赞

没事,我们有时候也会回复比较晚 :wink: 能帮到你就好

OK,成功了,一开始没用上面这个类,并且一开始推送的事件是在屏幕中直接推送的,导致的事件未触发

有个问题,就是我用

@Component
public class FooEventListener {

    @Autowired
    private UiEventPublisher uiEventPublisher;

    @TransactionalEventListener
    public void onFooChangedAfterCommit(EntityChangedEvent<Foo> event) {
        uiEventPublisher.publishEvent(new FooChangedEvent(this));
    }
}

发送的XXX实体变更的事件,然后在 XXX实体的页面监听如下:

@EventListener
    private void gameLibChanged(GameLibChangedEvent event) {
        gameLibsDl.load();

推送事件与监听都正常,可是监听端数据容易并未刷新,请问有什么方法解决么,也就是好像刷新这个数据容器的时候数据库并未更新