在服务 REST 中注入 WebSocket



我需要在创建项目后向客户端发送消息。该项将创建为 ApiRest。然后我用@ApplicationScope创建了我的 WebSocket,并使用 @Inject 在 serviceREST 中注入了我。问题是当webSocket初始化时,在我的serviceRest中,这个webSocket的会话仍然是空的。 我怎样才能在我的 apirrest 中使用网络 SOcket?

@Path("citas")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class citaResource {
@Inject
com.softcase.citasmanager.websocket.ws websocket;
@GET
@Path("cita")
@Produces("application/json")
public Response cita() {
websocket.onMessage("Your Item was created");//Session of webSocket is null
return Response.ok("ItemCreated", MediaType.APPLICATION_JSON).build();
}
}
@ApplicationScope
@ServerEndpoint("/item")
public class ws{   
private Session session;
@OnOpen
public void open(Session session) {
this.session = session;
}
@OnMessage
public void onMessage(String message) {
this.session.getBasicRemote().sendText(message);
}

一点上下文

实例

:每个客户端-服务器对都有一个唯一的会话实例,即为每个连接到 WebSocket 服务器终结点的客户端创建一个会话实例。简而言之,唯一会话实例的数量等于连接的客户端的数量

来源: https://abhirockzz.gitbooks.io/java-websocket-api-handbook/content/lifecycle_and_concurrency_semantics.html

有关更多详细信息:https://tyrus-project.github.io/documentation/1.13.1/index/lifecycle.html

建议使用static变量,例如

// @ApplicationScope
@ServerEndpoint("/item")
public class ws{   
// something like
private static final Set<javax.websocket.Session> ALL_SESSIONS = new HashSet<>();
// ...
}

可以在此处找到一个示例。这是一种选择,但我认为它不能解决您的注射问题。

另一种选择是利用javax.websocket.Session#getOpenedSessions()方法,例如此聊天示例。但再一次,它并没有解决注射问题。

您的示例

您同时使用 websocket 和 REST。据我了解,流程是:

  1. 用户 A、B、C 已连接
  2. 用户 A 提交请求以citas/cita并接收 REST 响应
  3. 同时,A、B、C 收到 websocket 通知

所以,正如你所写的,一方面,你有

@Path("citas")
// ...
public class CitaResource{
// ...
}

// @ApplicationScope -> commented as irrelevant in your situation
@ServerEndpoint("/item")
public class ws{   
// ...
}

在该示例中,当用户 A 发出请求时,有一个CitaResource实例,并且连接了三个wsA、B、C 实例。但是,您对注入的看法是正确的:您需要在CitaResource中注入一些东西,但您需要一个始终可用的 bean,正如您所注意到的,websocket 实例不是一个好的选择,容器必须注入哪个会话?

Websocket 会话处理程序

解决方案是使用应用程序范围的 Bean 来处理所有现有会话。我从甲骨文教程中得到了它。它是这样的:

// com.softcase.citasmanager.websocket.SessionHandler
@ApplicatedScoped
@Named // optional
public class MySessionHandler{
private final Set<Session> ALL_SESSIONS;
// or use a map if you need to identify the
// the session by a key. This example uses Set
// private final Map<String, Session> ALL_SESSIONS;
public MySessionHandler(){
ALL_SESSIONS = new HashSet<>();
}
// manage sessions
public void addSession(Session session){
this.ALL_SESSIONS.add(session);
}
public void removeSession(Session session){
this.ALL_SESSIONS.remove(session);
}
// send messages to all instances:
public void sendMessage(String message){
this.ALL_SESSIONS.stream()
// optional
.filter(s -> s.isOpen())
// or whatever method you want to send a message
.forEach( s -> s.getBasicRemote().sendText(message);
}
// or if you want to target a specific session
// hence my questions in comments
public void sendMessage(String message, String target){
this.ALL_SESSIONS..stream()
// identity the target session
.filter(s -> s.equals(target))
// optional
.filter(s -> s.isOpen())
.forEach( s -> s.getBasicRemote().sendText(message);
}
}

注意:

  • 我可以选择检查存储的会话是否仍处于打开状态。isOpen()不是强制性的,但它可能会避免一些错误
  • 将会话处理程序视为"船长":它知道有关 websocket 会话的所有信息,而会话本身彼此不知道。

但是,您需要调整终端节点以使会话处理程序高效:

// com.softcase.citasmanager.websocket.WsCita
@ServerEndpoint
public class WsCita{
// there is no need to declare the session as attribute
// private Session session;
// ApplicatedScoped so always defined
@Inject
private MySessionHandler handler;
@OnOpen
public void open(Session session){
handler.addSession(session);    // "Aye cap'tain, reporting from duty!"
// your stuff
}
@OnClose
public void close(Session session, CloseReason closeReason){
handler.removeSession(session); // "Cya cap'tain, that's all for today!"
// your stuff
}
// your OnMessage and other stuff here
}

现在我们已经设置了我们的 websocket 架构,现在怎么办?

  1. 每个客户端有一个WsCita实例。在任何时候,都可能有零个、一个或多个实例。
  2. MySessionHandler知道这些信息并且@ApplicatedScoped,因此可以安全地注入它

然后,REST 端点将更改为:

@Path("citas")
// ...
public class citaResource {
@Inject
com.softcase.citasmanager.websocket.SessionHandler handler;
@GET
// ...
public Response cita() {
// REST processing
// ...
// Websocket processing:
// - Handler is always here for you
// - Handler knows which websocket sessions to send the message to.
//   The RestController is not aware of the recipients
handler.sendMessage("Your Item was created");
}
}

请注意,我将 websocket 处理放在 REST 处理之后,因为您可能并不总是发送消息(例如创建或任何异常(。

杂项

与您的问题无关,但我对您的代码有一些评论:

  • 类名称是驼峰大小写,根据 Oracle 建议以大写字母开头
  • 避免类的泛型名称,例如Ws。我将其重命名WsCita例如

最新更新