Небольшой контекст
Экземпляры : для каждой пары клиент-сервер существует уникальный экземпляр Session, т. Е. Для каждого клиента, который подключается к WebSocket, создается один экземпляр Sessionконечная точка сервера.Короче говоря, количество уникальных экземпляров Session равно числу подключенных клиентов
Источник: https://abhirockzz.gitbooks.io/java-websocket-api-handbook/content/lifecycle_and_concurrency_semantics.html
Для более подробной информации: https://tyrus -project.github.io / Документация / 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 получают уведомление от веб-сокета
Итак, как вы писали, с одной стороны, у вас есть
@Path("citas")
// ...
public class CitaResource{
// ...
}
и
// @ApplicationScope -> commented as irrelevant in your situation
@ServerEndpoint("/item")
public class ws{
// ...
}
В этом примере есть один экземпляр CitaResource
, когда пользователь A сделал запрос, и три экземпляра ws
при подключении A, B, C.Тем не менее, вы были правы в отношении внедрения: вам нужно что-то внедрить в CitaResource
, но вам нужен всегда доступный компонент, и, как вы заметили, экземпляры websocket не являются хорошим вариантом, и какой сеанс должен вводить контейнер?
Обработчик сеансов websocket
Решение состоит в том, чтобы использовать бин области действия приложения для обработки всех существующих сеансов.Я получил это из Oracle учебник .Это выглядит так:
// 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
}
Теперь мы установили нашу архитектуру веб-сокетов, что теперь?
- У вас есть один экземпляр
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");
}
}
Пожалуйстаобратите внимание, что я помещаю обработку веб-сокета после обработки REST, поскольку вы не всегда можете отправлять сообщение (например, создание или любое другое исключение).
Разное
Не имеет отношения к вашим вопросам, но у меня есть некоторые комментарии оВаш код:
- Имя класса - CamelCase, начинающийся с заглавной буквы в соответствии с рекомендацией Oracle
- Избегайте использования общего имени для ваших классов, например
Ws
.Я переименовал его WsCita
для примера