在服务REST中注入WebSocket

时间:2018-10-09 14:09:28

标签: java rest dependency-injection websocket scope

创建项目后,我需要向客户发送消息。该项目将创建一个ApiRest。然后,我使用@ApplicationScope创建了WebSocket,并使用@Inject注入了serviceREST。问题是当webSocket初始化时,在我的serviceRest中,此webSocket的会话仍然为null。 如何在apirest中使用网络套接字?

@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);
    }

1 个答案:

答案 0 :(得分:0)

一点背景

  

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

来源: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

建议使用像{p>这样的static变量

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

    // ...
}

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

另一个选择是利用javax.websocket.Session#getOpenedSessions()之类的chat example方法。再次,它不能解决注入问题。

您的示例

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

  1. 用户A,B,C已连接
  2. 用户A向citas/cita提交请求并收到REST响应
  3. 同时,A,B,C收到网络套接字通知

因此,正如您所写,一方面,您拥有

@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处理所有现有会话。我是从Oracle tutorial获得的。它是这样的:

// 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处理之后,因为您可能并不总是发送消息(例如创建或任何异常)。

其他

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

  • 类名称为CamelCase,大写字母per Oracle recommendation
  • 避免使用类的通用名称,例如Ws。在示例中,我将其重命名为WsCita