我需要在创建项目后向客户端发送消息。该项将创建为 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。据我了解,流程是:
- 用户 A、B、C 已连接
- 用户 A 提交请求以
citas/cita
并接收 REST 响应 - 同时,A、B、C 收到 websocket 通知
所以,正如你所写的,一方面,你有
@Path("citas")
// ...
public class CitaResource{
// ...
}
和
// @ApplicationScope -> commented as irrelevant in your situation
@ServerEndpoint("/item")
public class ws{
// ...
}
在该示例中,当用户 A 发出请求时,有一个CitaResource
实例,并且连接了三个ws
A、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 架构,现在怎么办?
- 每个客户端有一个
WsCita
实例。在任何时候,都可能有零个、一个或多个实例。 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
例如