@OnOpenpublic void onOpen(会话会话,EndpointConfig 配置){User user = (User) config.getUserProperties().get("user");//...}你应该在应用范围内收集它们.您可以在端点类的 static 字段中收集它们.或者,更好的是,如果 WS 端点中的 CDI 支持在您的环境中没有被破坏(在 WildFly 中工作,而不是在 Tomcat+Weld 中,不确定 GlassFish),那么只需将它们收集在应用程序范围的 CDI 托管 bean 中,然后您又@Inject 在端点类中.当 User 实例不是 null 时(即当用户登录时),请记住一个用户可以有多个 WS 会话.因此,您基本上需要将它们收集在 Map 结构中,或者可能是更细粒度的映射,通过用户 ID 或组/角色来映射它们,毕竟,这可以更轻松地找到特定用户.这一切都取决于最终的要求.这里至少有一个使用应用程序范围的 CDI 托管 bean 的启动示例:@ApplicationScoped公共类 PushContext {私人地图<用户,设置<会话>>会议;@PostConstruct公共无效初始化(){session = new ConcurrentHashMap();}无效添加(会话会话,用户用户){session.computeIfAbsent(user, v -> ConcurrentHashMap.newKeySet()).add(session);}无效删除(会话会话){session.values().forEach(v -> v.removeIf(e -> e.equals(session)));}}@ServerEndpoint(value=/push", configurator=UserAwareConfigurator.class)公共类 PushEndpoint {@注入私有 PushContext pushContext;@OnOpenpublic void onOpen(会话会话,EndpointConfig 配置){User user = (User) config.getUserProperties().get("user");pushContext.add(session, user);}@OnClose公共无效 onClose(会话会话){pushContext.remove(session);}}最后,您可以在 PushContext 中向特定用户发送消息,如下所示:public void send(Set users, String message) {设置用户会话;同步(会话){userSessions = session.entrySet().stream().filter(e -> users.contains(e.getKey())).flatMap(e -> e.getValue().stream()).collect(Collectors.toSet());}对于(会话用户会话:用户会话){如果(userSession.isOpen()){userSession.getAsyncRemote().sendText(message);}}}PushContext 作为 CDI 托管 bean 具有额外的优势,即它可以注入任何其他 CDI 托管 bean,从而更容易集成.与关联用户一起触发 CDI 事件在您的 EntityListener 中,您最有可能根据之前的相关问题触发 CDI 事件使用 JSF/Java EE 实时更新数据库,您已经掌握了更改的实体,因此您应该能够找到与之关联的用户通过它们在模型中的关系.仅通知负责修改相关实体的用户(可能是管理员用户或注册用户,只有在成功登录后才能进行修改)@PostUpdate公共无效 onChange(实体实体){设置编辑器 = entity.getEditors();beanManager.fireEvent(new EntityChangeEvent(editors));}仅通知特定用户(不是全部).特定"例如,当一个帖子在本网站上被投票时,只有帖子所有者会收到通知(该帖子可能被任何其他具有足够权限的用户投票).@PostUpdate公共无效 onChange(实体实体){用户所有者 = entity.getOwner();beanManager.fireEvent(new EntityChangeEvent(Collections.singleton(owner)));}然后在 CDI 事件观察器中,传递它:public void onEntityChange(@Observes EntityChangeEvent 事件) {pushContext.send(event.getUsers(), "message");}另见:RFC6455 - WebSocket 协议(描述 ws:// 协议)W3 - WebSocket API(描述 JS WebSocket接口)MDN - 编写 WebSocket 客户端应用程序(描述如何在客户端使用 WS API)Java EE 7 教程 - WebSocket(描述 javax.websocket API 以及如何使用)使用 JSF/Java EE 从数据库实时更新(如果您已经在使用 JSF)In order to notify all users through WebSockets, when something is modified in selected JPA entities, I use the following basic approach.@ServerEndpoint("/Push")public class Push { private static final Set<Session> sessions = new LinkedHashSet<Session>(); @OnOpen public void onOpen(Session session) { sessions.add(session); } @OnClose public void onClose(Session session) { sessions.remove(session); } private static JsonObject createJsonMessage(String message) { return JsonProvider.provider().createObjectBuilder().add("jsonMessage", message).build(); } public static void sendAll(String text) { synchronized (sessions) { String message = createJsonMessage(text).toString(); for (Session session : sessions) { if (session.isOpen()) { session.getAsyncRemote().sendText(message); } } } }}When a selected JPA entity is modified, an appropriate CDI event is raised which is to be observed by the following CDI observer.@Typedpublic final class EventObserver { private EventObserver() {} public void onEntityChange(@Observes EntityChangeEvent event) { Push.sendAll("updateModel"); }}The observer/consumer invokes the static method Push#sendAll() defined in the WebSockets endpoint which sends a JSON message as a notification to all the associated users/connections.The logic inside the sendAll() method needs to be modified somehow, when only selected users are to be notified.Notify only the user who is responsible for modifying the entity in question (it may be an admin user or a registered user who can modify something only after their successful login).Notify only specific user/s (not all). "Specific" means for example, when a post is voted up on this site, only the post owner is notified (the post may be voted up by any other user with sufficient privileges).When an initial handshake is establised, HttpSession can be accessed as stated in this answer but it is still insufficient to accomplish the tasks as mentioned above by two bullets. Since it is available when the first handshake request is made, any attribute set to that session afterwards will not be available in the server endpoint i.e. in other words, any session attribute set after a handshake is established will not be available.What is the most acceptable/canonical way to notify only selected users as mentioned above? Some conditional statement(s) in the sendAll() method or something else somewhere is required. It appears that it has to do something other than only the user's HttpSession.I use GlassFish Server 4.1 / Java EE 7. 解决方案 Session?It seems that you got bitten by the ambiguity of the word "session". The lifetime of a session depends on the context and the client. A websocket (WS) session does not have the same lifetime as a HTTP session. Like as that an EJB session does not have the same lifetime as a HTTP session. Like as that a legacy Hibernate session does not have the same lifetime as a HTTP session. Etcetera. The HTTP session, which you likely already understand, is explained here How do servlets work? Instantiation, sessions, shared variables and multithreading. The EJB session is explained here JSF request scoped bean keeps recreating new Stateful session beans on every request?WebSocket lifecycleThe WS session is tied to the context represented by the HTML document. The client is basically the JavaScript code. The WS session starts when JavaScript does new WebSocket(url). The WS session stops when JavaScript explicitly invokes close() function on the WebSocket instance, or when the associated HTML document gets unloaded as result of a page navigation (clicking a link/bookmark or modifying URL in browser's address bar), or a page refresh, or a browser tab/window close. Do note that you can create multiple WebSocket instances within the very same DOM, usually each with different URL path or query string parameters.Each time when a WS session starts (i.e. each time when JavaScript does var ws = new WebSocket(url);), then this will fire a handshake request wherein you thus have access to the associated HTTP session via the below Configurator class as you already found out:public class ServletAwareConfigurator extends Configurator { @Override public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) { HttpSession httpSession = (HttpSession) request.getHttpSession(); config.getUserProperties().put("httpSession", httpSession); }}This is thus not called only once per HTTP session or HTML document as you seemed to expect. This is called every time a new WebSocket(url) is created.Then a brand new instance of the @ServerEndpoint annotated class will be created and its @OnOpen annotated method will be invoked. If you're familiar with JSF/CDI managed beans, just treat that class as if it's a @ViewScoped and the method as if it's a @PostConstruct.@ServerEndpoint(value="/push", configurator=ServletAwareConfigurator.class)public class PushEndpoint { private Session session; private EndpointConfig config; @OnOpen public void onOpen(Session session, EndpointConfig config) { this.session = session; this.config = config; } @OnMessage public void onMessage(String message) { // ... } @OnError public void onError(Throwable exception) { // ... } @OnClose public void onClose(CloseReason reason) { // ... }}Note that this class is unlike e.g. a servlet not application scoped. It's basically WS session scoped. So each new WS session gets its own instance. That's why you can safely assign Session and EndpointConfig as an instance variable. Depending on the class design (e.g. abstract template, etc), you could if necessary add back Session as 1st argument of all those other onXxx methods. This is also supported.The @OnMessage annotated method will be invoked when JavaScript does webSocket.send("some message"). The @OnClose annotated method will be called when the WS session is closed. The exact close reason can if necessary be determined by close reason codes as available by CloseReason.CloseCodes enum. The @OnError annotated method will be called when an exception is thrown, usually as an IO error on the WS connection (broken pipe, connection reset, etc).Collect WS sessions by logged-in userComing back to your concrete functional requirement of notifying only specific users, you should after the above explanation understand that you can safely rely on modifyHandshake() to extract the logged-in user from the associated HTTP session, every time, provided that new WebSocket(url) is created after the user is logged-in.public class UserAwareConfigurator extends Configurator { @Override public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) { HttpSession httpSession = (HttpSession) request.getHttpSession(); User user = (User) httpSession.getAttribute("user"); config.getUserProperties().put("user", user); }}Inside the WS endpoint class with a @ServerEndpoint(configurator=UserAwareConfigurator.class), you can get hand of it in @OnOpen annotated method as below:@OnOpenpublic void onOpen(Session session, EndpointConfig config) { User user = (User) config.getUserProperties().get("user"); // ...}You should collect them in the application scope. You can collect them in a static field of the endpoint class. Or, better, if CDI support in WS endpoint is not broken in your environment (works in WildFly, not in Tomcat+Weld, not sure about GlassFish), then simply collect them in an application scoped CDI managed bean which you in turn @Inject in the endpoint class.When User instance is not null (i.e. when an user is logged in), then remember that an user can have multiple WS sessions. So, you'd basically need to collect them in a Map<User, Set<Session>> structure, or perhaps a more fine grained mapping which maps them by user ID or group/role instead, which after all allows easier finding specific users. It all depends on the final requirements. Here's at least a kickoff example using an application scoped CDI managed bean:@ApplicationScopedpublic class PushContext { private Map<User, Set<Session>> sessions; @PostConstruct public void init() { sessions = new ConcurrentHashMap<>(); } void add(Session session, User user) { sessions.computeIfAbsent(user, v -> ConcurrentHashMap.newKeySet()).add(session); } void remove(Session session) { sessions.values().forEach(v -> v.removeIf(e -> e.equals(session))); }}@ServerEndpoint(value="/push", configurator=UserAwareConfigurator.class)public class PushEndpoint { @Inject private PushContext pushContext; @OnOpen public void onOpen(Session session, EndpointConfig config) { User user = (User) config.getUserProperties().get("user"); pushContext.add(session, user); } @OnClose public void onClose(Session session) { pushContext.remove(session); }}Finally you can send a message to specific user(s) as below in PushContext:public void send(Set<User> users, String message) { Set<Session> userSessions; synchronized(sessions) { userSessions = sessions.entrySet().stream() .filter(e -> users.contains(e.getKey())) .flatMap(e -> e.getValue().stream()) .collect(Collectors.toSet()); } for (Session userSession : userSessions) { if (userSession.isOpen()) { userSession.getAsyncRemote().sendText(message); } }}The PushContext being a CDI managed bean has the additional advantage that it's injectable in any other CDI managed bean, allowing easier integration.Fire CDI event with associated usersIn your EntityListener, where you fire the CDI event most likely as per your previous related question Real time updates from database using JSF/Java EE, you already have the changed entity at hands and thus you should be able to find the users associated with it via their relationships in the model.@PostUpdatepublic void onChange(Entity entity) { Set<User> editors = entity.getEditors(); beanManager.fireEvent(new EntityChangeEvent(editors));}@PostUpdatepublic void onChange(Entity entity) { User owner = entity.getOwner(); beanManager.fireEvent(new EntityChangeEvent(Collections.singleton(owner)));}And then in the CDI event observer, pass it forth:public void onEntityChange(@Observes EntityChangeEvent event) { pushContext.send(event.getUsers(), "message");}See also:RFC6455 - The WebSocket Protocol (describes ws:// protocol)W3 - The WebSocket API (describes JS WebSocket interface)MDN - Writing WebSocket client application (describes how to use WS API in client)Java EE 7 tutorial - WebSocket (describes javax.websocket API and how to use it)Real time updates from database using JSF/Java EE (in case you're already using JSF) 这篇关于当数据库中的某些内容被修改时,通过 WebSockets 仅通知特定用户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云! 08-06 09:10