如何缩小 websocket 服务器端点和会话范围烘焙 bean 之间的差距

How close the gab between websocket server endpoint and session scoped backing beans

简而言之,我不知道如何从应用程序范围的 bean 通知会话范围的 bean。我发现了一个可行的脏 hack,但我不确定是否有更好的方法来解决这个问题(或者我的脏 hack 不脏 :-D )。

javaee 中的每个 websocket 都有一个 sessionid。但是这个sessionid和jsf里的不一样,没办法简单映射。在我的环境中,我有一个 jsf 网页、一个底层会话范围的支持 bean 和一个 websocket,它通过 jms 连接到外部服务。当加载 jsf 页面,并且 websocket 也连接到浏览器时,支持 bean 向外部服务发送请求。当我通过 jms 收到异步应答消息时,我不知道哪个 websocket 与发送请求的 jsf page/backing bean 连接。

为了用部分肮脏的 hack 解决这个问题,我编写了一个应用程序范围的调解器 class。

@Named
@ApplicationScoped
public class WebsocketMediator {

    @Inject
    private Event<UUID> notifyBackingBeans;

    private Integer newSequenceId=0;

    // I need this map for the dirty hack
    private Map<UUID, BackingBean> registrationIdBackingBeanMap;

    private Map<Integer, UUID> sequenceIdRegistrationIdMap;
    registrationIdWebSocketMap = new ConcurrentHashMap<>();

    public UUID register(BackingBean backingBean) {
        UUID registrationId = UUID.randomUUID();

        registrationIdSequenceMap.put(registrationId, new HashSet<>());
        registrationIdBackinBeanMap.put(registrationId, backingBean);
    }

    public Integer getSequenceId(UUID registrationId) {
        sequenceId++;
        sequenceIdRegistrationIdMap.put(sequenceId, registrationId);
        registrationIdSequenceMap.get(registrationId).add(sequenceId);
        return sequenceId;
    }

    // this is called from the ws server enpoint
    public void registerWebsocket(UUID registrationId, Session wsSession) {
        registrationIdWebSocketMap.put(registrationId, wsSession);
        websocketRegistrationIdMap.put(wsSession.getId(), registrationId);
        notifyBackingBeans.fire(registrationId); // This does not work

        SwitchDataModel switchDataModel = registrationIdSwitchDataModelMap.get(registrationId);
        if (backingBean != null) {
            backingBean.dirtyHackTrigger();
        }
    }

    public void unregisterWebsocket(String wsSessionId) {
         ...
    }
}

backing bean 调用一个注册方法并获得一个uniq 随机注册id (uuid)。注册 id 作为隐藏数据属性 (f:passTrough) 放在一个 jsf table 中。当连接 websocket 时,在浏览器中调用 ws.open 函数并通过 websocket 将注册 id 发送到 websocket 服务器端点 class。服务器端点 class 调用中介中的 public void registerWebsocket(UUID registrationId, Session wsSession) 方法并映射注册 ID。当支持 bean 超时时,我从 @PreDestroyed 注释方法调用注销方法。每次通过 jms 调用外部系统时,我都会将一个序列 ID 放入有效负载中。 Sequence Id 在 Mediator class 中注册。每次外部系统发送消息时,我都可以在调解器中查找正确的 websocket,以通过 websocket 将消息绕过到正确的浏览器。

现在系统可以通过外部系统接收异步事件,但支持 bean 不知道。我试图将 cdi 事件中的注册 ID 发送到会话范围的支持 bean,但该事件永远不会到达会话范围的支持 bean 中的观察者。所以我用一个肮脏的技巧意识到了这个空谈。我将每个以注册 id 作为键的支持 bean 的实例放入注册方法中的中介中的映射中。我在 public void registerWebsocket(UUID registrationId, Session wsSession) 中放置了支持 bean 的肮脏 hack 触发器调用。有更好的解决方案吗?

我将 Wildfly 13 与 CDI 1.2 一起使用。

提前致谢!

我找到了解决办法。调用网页时,将创建 @SessionScoped bean。我计算了一个唯一的注册 ID 并将其作为属性放在 HttpSession:

@PostConstruct
public void register() {
    registrationId = switchPortMediator.register(this);
    HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
    session.setAttribute("switchRegistrationId", registrationId);
    log.debug("Registered at mediator - ID=" + registrationId + " http session = "+ session.getId());
}

@ServerEndpoint 带注释的 Web 套接字端点处,是 @OnOpen 带注释的方法。当加载网页并建立 websocket 时调用此方法。 Websocket 端点 class 是 @ApplicationScoped。来自存储注册 ID 的 @SessionScoped bean 的属性映射可在 websocket 端点的 EndpointConfig 中访问。这是@OnOpen 注释的方法:

@OnOpen
public void onOpen(Session wsSession, EndpointConfig config) {
    UUID registrationId = (UUID) config.getUserProperties().get("switchRegistrationId");
   websocketRegistrationIdMap.put(registrationId, wsSession);
}

加载 JSF 页面时,@SessionScoped bean 计算注册 ID,将其放入属性映射中并通过 jms 向外部系统发送异步消息。外部系统发送应答消息,其中包含注册 ID 和有效负载。当外部消息通过 JMS 到达 websocket 端点 class 时,可以从 websocketRegistrationIdMap 中检索生成的 wsSession,并且可以通过 websocket 将有效负载发送到启动异步消息的浏览器。网站中的 dom 更新由 javascript 处理。