web-dev-qa-db-ja.com

既存のWebSocketインスタンスを取得する方法

Websocket(Java EE 7)を使用して、接続されているすべてのクライアントに非同期でメッセージを送信するアプリケーションに取り組んでいます。サーバー(Websocketエンドポイント)は、新しい記事(アプリのエンゲージメントモーダル)が作成されるたびにこれらのメッセージを送信する必要があります。

WebSocketエンドポイントへの接続が確立されるたびに、対応するセッションをリストに追加します。これにより、外部からアクセスできるようになります。

しかし、私が抱えていた問題は、すべてのクライアントが外部(他のビジネスクラス)から接続しているこの作成されたWebSocketエンドポイントにアクセスすると、既存のインスタンス(シングルトンなど)を取得することです。

それで、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;
}

前もって感謝します。

14
Aryan Venkat

コンテナは、クライアント接続ごとにエンドポイントの個別のインスタンスを作成するため、実行しようとしていることを実行できません。しかし、あなたがやろうとしているのは、イベントが発生したときにすべてのアクティブなクライアント接続にメッセージを送信することだと思います。これはかなり簡単です。

_javax.websocket.Session_クラスには、そのセッションに関連付けられたエンドポイントを表す_RemoteEndpoint.Basic_インスタンスを取得するためのgetBasicRemoteメソッドがあります。

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イベントを使用して、すべてのクライアントへの更新をトリガーすることをお勧めします。その場合、Websocketエンドポイントクラスのメソッドが監視するCDIイベントを作成します。

_@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 EE7チュートリアルの章を参照してください。

編集:_@Observer_メソッドを変更して、イベントをパラメーターとして使用するようにしました。

編集2:@gcvtに従って、broadcastArticleのループを同期でラップしました。

編集3:Java EE 7チュートリアルへのリンクを更新しました。お疲れ様でした。Oracle。Sheesh。

20
Ian Evans

実際、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));
    }

もちろん、すべてのエンドポイントに1つのコンフィギュレーターを使用するか(提示されたスニペットのように醜い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 を参照してください。 =完全なテスト用。

9
Pavel Bucek