简而言之,我不知道如何从应用程序范围的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一起使用。
提前谢谢!
答案 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处理。