1

インターフェイスに基づいてイベントハンドラーを実装したいと思いjavax.enterprise.event.Eventます。現在はうまく機能しますが、1つのセッションに限定されています。私の環境はJBoss-7.1.1.FinalJSF2です。この簡略化された(機能する)例で問題を示すことができます。

@NamedBean内で、を定義し、Event<MyEjb>イベントを発生させることができます。

@Inject private Event<MyEJB> ejbChanged;

public void test()
{
  ejbChanged.fire(new MyEjb());
}

これは@Named @SessionScopedBeanで認識され、簡単に処理できます

public void
       onChanged(@Observes(notifyObserver = Reception.IF_EXISTS) final MyEjb ejb)
{
  logger.info("onChanged "+ejb.toString());
}

ただし、残念ながら、は現在のユーザーセッションに属するBeanでonChangedのみ呼び出されます。@SessionScopedしかし、他のユーザーセッションに属するBeanにも到達したいと思います。これは、このCDIイベント処理で可能ですか?

4

2 に答える 2

3

MarkusDahmがブログエントリで説明しているソリューション

Webアプリケーションのすべてのアクティブなセッションにイベントを送信する彼のソリューションには、グローバルイベントをサポートするために既存のCDIオブザーバーパターンを拡張することが含まれます。

関連するコードは次のとおりです。

まず、すべてのHTTPセッションを登録して処理し、すべてのイベントをセッションに委任するGlobalevHttpSessionControllerが必要です。

@ApplicationScoped
public class GlobalevHttpSessionController {
  public static final String 
    EVENT_ATTRIBUTE_NAME = "HttpSessionControllerEvent";

  private final List _httpSessions = new ArrayList();

  public List getHttpSessions() {
    return new ArrayList(_httpSessions);
  }

  public void addSession(final HttpSession httpSession) {
    _httpSessions.add(httpSession);
  }

  public void removeSession(final HttpSession httpSession) {
    _httpSessions.remove(httpSession);
  }

  public void fireEvent(final GlobalEvent eventObject) {
    for (final HttpSession session : _httpSessions) {
      fireEvent(session, eventObject);
    }
  }

  private void fireEvent(final HttpSession session, final GlobalEvent eventObject) {
    try {
      final List globalEvents = getGlobalEvents(session);

      globalEvents.add(eventObject);
    } catch (final Exception e) {
      throw new IllegalStateException("fireEvent", e);
    }
  }

  private synchronized List getGlobalEvents(final HttpSession session) {
    List globalEvents = (List) session.getAttribute(EVENT_ATTRIBUTE_NAME);

    if (globalEvents == null) {
      globalEvents = new ArrayList();
      session.setAttribute(EVENT_ATTRIBUTE_NAME, globalEvents);
    }

    return globalEvents;
  }
}

GlobalEventは、すべてのグローバルイベント用の単純なシリアル化可能なスーパークラスです。

public abstract class GlobalEvent implements Serializable {
  private static final long serialVersionUID = 1L;
}

次に、アクティブなクライアントセッションを追加および削除するためのHTTPリスナーが必要です。

@WebListener
public class GlobalevHttpSessionListener implements HttpSessionListener {
  @Inject
  private GlobalevHttpSessionController _httpSessionController;

  public void sessionCreated(final HttpSessionEvent se) {
    _httpSessionController.addSession(se.getSession());
  }

  public void sessionDestroyed(final HttpSessionEvent se) {
    _httpSessionController.removeSession(se.getSession());
  }
}

そして、クライアントにイベントをディスパッチするために、彼はJSFPhaseListenerを使用して着信イベントを検索することを選択しました。たとえばフィルターを使用する他の解決策も可能です。リスナーはグローバルイベントを探し、CDIBeanマネージャーを使用してそれらをローカルセッションにディスパッチします。マネージャインスタンスは、JNDIルックアップを介して取得されます(仕様により、Beanマネージャはjava:comp / BeanManagerの下のコンテナによってバインドされる必要があります)。残念ながら、フェーズリスナーはCDIではなく、Java Server Faces(JSF)フレームワークによってインスタンス化されるため、ここではCDIインジェクションを使用できません。ただし、フレームワークは、FacesContextオブジェクトを介してHTTPセッションにアクセスするために必要なすべての情報を提供します。

public class GlobalevEventPhaseListener implements PhaseListener {
  public void beforePhase(final PhaseEvent event) {
    final FacesContext facesContext = event.getFacesContext();
    final HttpSession httpSession =   
        JSFUtil.getHttpSession(facesContext);

    if (httpSession != null) {
      final List globalEvents = getGlobalEvents(httpSession);

      if (!globalEvents.isEmpty()) {
        fireEvents(globalEvents);
      }
    }
  }

  private void fireEvents(final List globalEvents) {
    final BeanManager beanManager = lookBeanManager();

    if (beanManager != null) {
      try {
        for (final GlobalEvent devaGlobalEvent : globalEvents) {
          beanManager.fireEvent(devaGlobalEvent);
        }
      } catch (final Exception e) {
        throw new IllegalStateException("fireEvents", e);
      }
    }
  }

  @Override
  public PhaseId getPhaseId() {
    return PhaseId.RENDER_RESPONSE; // RESTORE_VIEW;
  }

  private BeanManager lookBeanManager() {
    try {
      final Object obj = 
        new InitialContext().lookup("java:comp/BeanManager");

      return (BeanManager) obj;
    } catch (final Exception e) {
           throw new 
            IllegalStateException("Lookup bean manager", e);
    }

    return null;
  }

  private synchronized List getGlobalEvents(final HttpSession httpSession) {
    final List events = (List) httpSession.getAttribute(
        GlobalevHttpSessionController.EVENT_ATTRIBUTE_NAME);
    final List result = new ArrayList();

    if (events != null) {
      result.addAll(events);
      events.clear();
    }

    return result;
  }
}

最後に、リスナーをfaces-config.xmlに登録する必要があります。

<lifecycle>
  <phase-listener>
de.akquinet.jbosscc.globalev.listener.GlobalevEventPhaseListener
  </phase-listener>
</lifecycle>

コードはgithubでも入手できます

Markusに感謝します!

于 2012-10-02T14:54:02.807 に答える
0

私はこれを一度考えましたが、それでは、このアプローチはどの程度スケーラブルですか?何千ものアクティブなセッションがあった場合はどうなりますか?イベントのコールバックメソッドは、発生するすべてのイベントの各コンテキストで数千のメソッド呼び出しがあるため、非常に軽量である必要があります。

すべてのアクティブなセッションに常に通知する必要はありません。このメカニズムを拡張し、特定のセッションを特定のイベントにサブスクライブさせることで、メソッド呼び出しの数を制御できます。

何をすべきかは要件によって異なります。データが頻繁に更新される場合は、セッションBeanにアプリケーションスコープのBeanに変更をポーリングさせることをお勧めします。このように、セッションBeanは、からの実際の要求に応じてのみデータを更新します。ユーザー。これにより、現在データを消費しているBeanのみが最新の状態に保たれます。

繰り返しになりますが、誰かがこれを手伝ってくれるかもしれませんが、これはスケーラブルな設計にはつながらないと思います。ある日、負荷分散を使用することにし、サーバーのクラスターがあり、すべてのアクティブなセッションにイベントを伝播する必要がある場合はどうでしょうか。すべてのサーバーなどで。正当な理由で仕様がこれを実装していなかったと思います。サーバーのワークロードをユーザーが必要とするものに最小限に抑える必要があります。また、コーヒーを飲みに行くためにコンピューターを離れたユーザーに属する、イベントで更新できるセッションBeanの数を考えてください。

于 2013-02-08T13:23:15.017 に答える