我正在开发一个应用程序,该应用程序使用Websockets(Java EE 7)向所有连接的客户端异步发送消息。每当创建一篇新文章(我的应用程序中的参与模式)时,服务器(Websocket端点)都应该发送这些消息。
每次建立到websocket端点的连接时,我都会将相应的会话添加到列表中,我可以在外部访问该列表。
但我遇到的问题是,当我访问这个创建的websocket端点时,所有客户端都从外部连接到该端点(任何其他业务类),我得到了现有的实例(就像一个singleton)。
所以,你能建议我一种方法来获得websocket端点的现有实例吗?因为我不能将其创建为新的MyWebsocketEndPoint(),因为每当收到来自客户端的请求时,它都会由websocket内部机制创建。
参考:
private static WebSocketEndPoint INSTANCE = null;
public static WebSocketEndPoint getInstance() {
if(INSTANCE == null) {
// Instead of creating a new instance, I need an existing one
INSTANCE = new WebSocketEndPoint ();
}
return INSTANCE;
}
提前谢谢。
容器为每个客户端连接创建一个单独的端点实例,所以你不能做你想做的事情。但我认为你想做是在事件发生时向所有活动的客户端连接发送一条消息,这很简单。
javax.websocket.Session
类具有getBasicRemote
方法来检索代表与该会话相关联的端点的RemoteEndpoint.Basic
实例。
您可以通过调用Session.getOpenSessions()
来检索所有打开的会话,然后对它们进行迭代。循环将向每个客户端连接发送一条消息。这里有一个简单的例子:
@ServerEndpoint("/myendpoint")
public class MyEndpoint {
@OnMessage
public void onMessage(Session session, String message) {
try {
for (Session s : session.getOpenSessions()) {
if (s.isOpen()) {
s.getBasicRemote().sendText(message);
}
} catch (IOException ex) { ... }
}
}
但在您的情况下,您可能希望使用CDI事件来触发对所有客户端的更新。在这种情况下,您将创建一个CDI事件,Websocket端点类中的方法将观察到该事件:
@ServerEndpoint("/myendpoint")
public class MyEndpoint {
// EJB that fires an event when a new article appears
@EJB
ArticleBean articleBean;
// a collection containing all the sessions
private static final Set<Session> sessions =
Collections.synchronizedSet(new HashSet<Session>());
@OnOpen
public void onOpen(final Session session) {
// add the new session to the set
sessions.add(session);
...
}
@OnClose
public void onClose(final Session session) {
// remove the session from the set
sessions.remove(session);
}
public void broadcastArticle(@Observes @NewArticleEvent ArticleEvent articleEvent) {
synchronized(sessions) {
for (Session s : sessions) {
if (s.isOpen()) {
try {
// send the article summary to all the connected clients
s.getBasicRemote().sendText("New article up:" + articleEvent.getArticle().getSummary());
} catch (IOException ex) { ... }
}
}
}
}
}
上面示例中的EJB将执行以下操作:
...
@Inject
Event<ArticleEvent> newArticleEvent;
public void publishArticle(Article article) {
...
newArticleEvent.fire(new ArticleEvent(article));
...
}
请参阅关于WebSockets和CDI事件的Java EE 7教程章节。
编辑:修改了@Observer
方法以使用事件作为参数。
编辑2:根据@gcvt,在synchronized中包装broadcastArticle中的循环。
编辑3:更新了Java EE 7教程的链接。干得好,甲骨文。Sheesh。
实际上,WebSocket API提供了一种控制端点实例化的方法。看见https://tyrus.java.net/apidocs/1.2.1/javax/websocket/server/ServerEndpointConfig.Configurator.html
简单样本(取自Tyrus-WebSocket RI测试):
public static class MyServerConfigurator extends ServerEndpointConfig.Configurator {
public static final MyEndpointAnnotated testEndpoint1 = new MyEndpointAnnotated();
public static final MyEndpointProgrammatic testEndpoint2 = new MyEndpointProgrammatic();
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
if (endpointClass.equals(MyEndpointAnnotated.class)) {
return (T) testEndpoint1;
} else if (endpointClass.equals(MyEndpointProgrammatic.class)) {
return (T) testEndpoint2;
}
throw new InstantiationException();
}
}
您需要将其注册到一个端点:
@ServerEndpoint(value = "/echoAnnotated", configurator = MyServerConfigurator.class)
public static class MyEndpointAnnotated {
@OnMessage
public String onMessage(String message) {
assertEquals(MyServerConfigurator.testEndpoint1, this);
return message;
}
}
或者您也可以将其与编程端点一起使用:
public static class MyApplication implements ServerApplicationConfig {
@Override
public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses) {
return new HashSet<ServerEndpointConfig>
(Arrays.asList(ServerEndpointConfig.Builder
.create(MyEndpointProgrammatic.class, "/echoProgrammatic")
.configurator(new MyServerConfigurator())
.build()));
}
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {
return new HashSet<Class<?>>(Arrays.asList(MyEndpointAnnotated.class));
}
当然,这取决于您是否为所有端点使用一个配置器(如所示片段中的丑陋if),或者是否为每个端点创建单独的配置器。
请不要照原样复制呈现的代码——这只是Tyrus测试的一部分,它确实违反了一些基本的OOM范式。
请参阅https://github.com/tyrus-project/tyrus/blob/1.2.1/tests/e2e/src/test/java/org/glassfish/tyrus/test/e2e/GetEndpointInstanceTest.java以进行完整的测试。