websocket服务器端点与会话作用域后备bean之间的差距如何缩小

时间:2018-09-04 15:21:56

标签: jsf java-ee websocket wildfly cdi

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

java ee中的每个websocket都有一个sessionid。但是,这个sessionid与jsf中的不一样,并且没有简单的映射方法。在我的环境中,我有一个jsf网页,一个基础的sessionscoped支持bean和一个websocket,它们通过jms连接到外部服务。当加载jsf页面并且websocket也连接到浏览器时,后备bean将请求发送到外部服务。当我通过jms收到异步答复消息时,我不知道哪个websocket与发送请求的jsf页面/支持bean连接。

为了通过部分肮脏的hack解决此问题,我编写了一个应用程序范围的介体类。

@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) {
         ...
    }
}

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

现在,系统能够通过外部系统接收异步事件,但是后备bean不知道这一点。我试图将cdi事件中的注册ID发送到会话作用域后备bean,但是该事件从未到达会话作用域后备bean中的观察者。所以我意识到这是一个肮脏的hack。我将每个注册ID为键的后备bean的实例放入注册方法中的介体中的映射中。我在public void registerWebsocket(UUID registrationId, Session wsSession)中放入了备用Bean的脏hack触发调用。有更好的解决方案吗?

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

提前谢谢!

1 个答案:

答案 0 :(得分:0)

我找到了解决方案。调用网页时,将创建@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端点类为@ApplicationScoped。来自@SessionScoped bean(用于存储注册ID)的属性映射可以在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终结点类中时,可以从websocketRegistrationIdMap中检索所得的wsSession,并且可以通过websocket将有效负载发送给启动异步消息的浏览器。网站中的dom更新由javascript处理。