JSF基于事件的交流:新派方法

上一篇文章中 ,我们学习了基于Observer / Event Listener和Mediator模式的基于事件的通信。 由于它们的缺点,我想展示基于事件的通信的更有效方法。 我们将从Google Guava EventBus开始,以CDI (Java EE平台的上下文和依赖注入)结束。

番石榴EventBus

Google Guava库具有有用的package eventbus 。 EventBus类允许组件之间进行发布-订阅式通信,而无需组件之间进行显式注册。 因为我们开发Web应用程序,所以我们应该将此类的实例封装在有作用域的bean中。

让我们编写EventBusProvider bean。

public class EventBusProvider implements Serializable {

    private EventBus eventBus = new EventBus("scopedEventBus");

    public static EventBus getEventBus() {
        // access EventBusProvider bean
        ELContext elContext = FacesContext.getCurrentInstance().getELContext();
        EventBusProvider eventBusProvider =
            (EventBusProvider) elContext.getELResolver().getValue(elContext, null, "eventBusProvider");

        return eventBusProvider.eventBus;
    }
}

我仅以一个示例来演示Guava EventBus的所有主要功能。 让我们编写以下事件层次结构:

public class SettingsChangeEvent {

}

public class LocaleChangeEvent extends SettingsChangeEvent {

    public LocaleChangeEvent(Object newLocale) {
        ...
    }
}

public class TimeZoneChangeEvent extends SettingsChangeEvent {

    public TimeZoneChangeEvent(Object newTimeZone) {
        ...
    }
}
下一步很简单。 要接收事件,对象(bean)应公开一个公共方法,该方法以@Subscribe批注进行批注,该方法接受具有所需事件类型的单个参数。 该对象需要将自身传递给EventBus实例的register()方法。 让我们创建两个bean:
public MyBean1 implements Serializable {

    @PostConstruct
    public void initialize() throws Exception {
        EventBusProvider.getEventBus().register(this);
    }

    @Subscribe
    public void handleLocaleChange(LocaleChangeEvent event) {
        // do something
    }

    @Subscribe
    public void handleTimeZoneChange(TimeZoneChangeEvent event) {
        // do something
    }
}

public MyBean2 implements Serializable {

    @PostConstruct
    public void initialize() throws Exception {
        EventBusProvider.getEventBus().register(this);
    }

    @Subscribe
    public void handleSettingsChange(SettingsChangeEvent event) {
        // do something
    }
}

要发布事件,只需将事件对象提供给EventBus实例的post()方法。 EventBus实例将确定事件的类型并将其路由到所有已注册的侦听器。

public class UserSettingsForm implements Serializable {

    private boolean changed;

    public void localeChangeListener(ValueChangeEvent e) {
        changed = true;        
 
        // notify subscribers
        EventBusProvider.getEventBus().post(new LocaleChangeEvent(e.getNewValue()));
    }

    public void timeZoneChangeListener(ValueChangeEvent e) {
        changed = true;        
 
        // notify subscribers
        EventBusProvider.getEventBus().post(new TimeZoneChangeEvent(e.getNewValue()));
    }

    public String saveUserSettings() {
        ...

        if (changed) {
            // notify subscribers
            EventBusProvider.getEventBus().post(new SettingsChangeEvent());

            return "home";
        }
    }
}
Guava EventBus允许创建对许多不同事件做出反应的任何侦听器,只需使用@Subscribe注释许多方法即可。 侦听器可以利用现有事件层次结构。 因此,如果侦听器A正在等待事件A,并且事件A具有名为B的子类,则此侦听器将接收两种类型的事件:A和B。在我们的示例中,我们发布了三个事件:SettingsChangeEvent,LocaleChangeEvent和TimeZoneChangeEvent。 MyBean1中的handleLocaleChange()方法将仅接收LocaleChangeEvent。 方法handleTimeZoneChange()将仅接收TimeZoneChangeEvent。 但是,请查看MyBean2中的handleSettingsChange()方法。 它将接收所有三个事件!
如您所见,仍然需要手动注册(EventBusProvider.getEventBus()。register(this)),并且在上一篇文章中提到的作用域bean的问题仍然存在。 我们应该注意EventBusProvider的作用域和发布/订阅者bean的作用域。 但是,正如您可能还会看到的,与Mediator模式相比,我们有了一些改进:不需要特殊的接口,没有固定定义订户的方法名,也可以进行多侦听器,不花精力管理注册的实例,等等。但并非最不重要的是-异步AsyncEventBus和对DeadEvent的订阅(用于侦听没有侦听器调度的任何事件-便于调试)。 请按照本指南将现有的基于EventListener的系统转换为基于EventBus的系统。
CDI(上下文和依赖注入)
每个符合JEE 6的应用服务器都支持CDI(JSR-299规范)。 它定义了一组补充服务,可帮助改善应用程序代码的结构。 CDI的最著名的实现是OpenWebBeansJBoss Weld 。 CDI中的事件允许bean完全不依赖地进行交互。 事件生产者引发事件,这些事件由容器传递给事件观察者。 这个基本架构听起来像熟悉的Observer / Observable模式,但是有很多好处。
  • 事件生产者和事件观察者彼此分离。
  • 观察者可以指定“选择器”的组合来缩小他们将接收的事件通知的范围。
  • 可以立即或延迟通知观察者,直到当前事务结束为止。
  • 使用条件观察者方法进行作用域定义时不会感到头痛(还记得作用域bean和Mediator / EventBus的问题吗?)。
条件观察者方法仅在声明观察者方法的bean范围当前处于活动状态时才允许获取已经存在的bean实例,而无需创建新的bean实例。 如果观察者方法不是有条件的,则将始终创建相应的bean。 您很灵活!
在我看来,CDI事件机制是进行基于事件的交流的最佳方法。 这个问题很复杂。 让我们只显示基本功能。 观察者方法是带有参数@Observes的bean的方法。
public MyBean implements Serializable {

    public void onLocaleChangeEvent(@Observes Locale locale) {
        ...
    }
}

如果观察者方法仅对限定的事件感兴趣,则事件参数也可以指定限定符-这些事件具有限定符。

public void onLocaleChangeEvent(@Observes @Updated Locale locale) {
    ...
}

事件限定符只是使用@Qualifier定义的普通限定符。 这是一个例子:

@Qualifier
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Updated {}

事件生产者使用参数化Event接口的实例触发事件。 该接口的实例通过注入获得。 生产者通过调用Event接口的fire()方法并传递事件对象来引发事件。

public class UserSettingsForm implements Serializable {

    @Inject @Any Event<Locale> localeEvent;

    public void localeChangeListener(ValueChangeEvent e) {
        // notify all observers
        localeEvent.fire((Locale)e.getNewValue());
    }
}
容器调用所有观察者方法,并将事件对象作为事件参数的值传递。 如果任何观察者方法引发异常,则容器将停止调用观察者方法,并且该异常将由fire()方法重新抛出。 上面的@Any注释充当所有限定符的别名。 您会看到,无需手动注册观察员。 简单? 在注入点指定其他限定词也很简单:
// this will raise events to observers having parameter @Observes @Updated Locale
@Inject @Updated Event<Locale> localeEvent;
您还可以具有多个事件限定符。 该事件将传递给每个具有事件参数的观察者方法,该事件参数可以分配事件对象,并且除了与事件注入点指定的事件限定符匹配的事件限定符之外,没有任何事件限定符。 观察者方法可能具有其他参数,这些参数是注入点。 例:
public void onLocaleChangeEvent(@Observes @Updated Locale locale, User user) {
    ...
}

动态指定限定符是什么? CDI允许通过AnnotationLiteral获得适当的限定符实例。 这样,我们可以将限定符传递给Event的select()方法。 例:

public class DocumentController implements Serializable {

    Document document;

    @Inject @Updated @Deleted Event<Document> documentEvent;

    public void updateDocument() {
        ...
        // notify observers with @Updated annotation
        documentEvent.select(new AnnotationLiteral<Updated>(){}).fire(document);
    }

    public void deleteDocument() {
        ...
        // notify observers with @Deleted annotation
        documentEvent.select(new AnnotationLiteral<Deleted>(){}).fire(document);
    }
}
让我们谈谈“条件观察者方法”。 默认情况下,如果当前上下文中没有观察者实例,则容器将实例化观察者以向其传递事件。 这种行为并不总是令人满意的。 我们可能只想将事件传递给当前上下文中已经存在的观察者实例。 通过在@Observes批注中添加receive = IF_EXISTS来指定条件观察者。
public void onLocaleChangeEvent(@Observes(receive = IF_EXISTS) @Updated Locale locale) {
    ...
}
在此处阅读有关范围和上下文的更多信息。 在这篇简短的文章中,我们不能再谈论更多功能,例如“具有成员的事件限定符”和“事务观察者”。 我想鼓励大家开始学习CDI。 玩得开心!
参考: JSF中基于事件的通信。 新学派的方法 。 来自我们的JCG合作伙伴 Oleg Varaksin,来自“软件开发思想”博客。

翻译自: https://www.javacodegeeks.com/2012/07/jsf-event-based-communication-new.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值