Гуава EventBus
В библиотеке Google Guava есть полезный пакет eventbus . Класс EventBus позволяет осуществлять связь между компонентами в стиле публикации-подписки, не требуя, чтобы компоненты явно регистрировались друг с другом. Поскольку мы разрабатываем веб-приложения, мы должны инкапсулировать экземпляр этого класса в bean-объект.
Давайте напишем бин EventBusProvider.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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 только в одном примере. Давайте напишем следующую иерархию событий:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public class SettingsChangeEvent { } public class LocaleChangeEvent extends SettingsChangeEvent { public LocaleChangeEvent(Object newLocale) { ... } } public class TimeZoneChangeEvent extends SettingsChangeEvent { public TimeZoneChangeEvent(Object newTimeZone) { ... } } |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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 } } |
Чтобы опубликовать событие, просто предоставьте объект события методу post () экземпляра EventBus. Экземпляр EventBus определит тип события и направит его всем зарегистрированным слушателям.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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" ; } } } |
- Производители событий и наблюдатели событий отделены друг от друга.
- Наблюдатели могут указать комбинацию «селекторов», чтобы сузить набор уведомлений о событиях, которые они будут получать.
- Наблюдатели могут быть уведомлены немедленно или с задержкой до конца текущей транзакции.
- Отсутствие головной боли при определении объема с помощью методов условного наблюдателя (помните проблему с областями действия и Mediator / EventBus?)
1
2
3
4
5
6
|
public MyBean implements Serializable { public void onLocaleChangeEvent( @Observes Locale locale) { ... } } |
Параметр события может также указывать квалификаторы, если метод наблюдателя заинтересован только в квалифицированных событиях — это события, которые имеют эти квалификаторы.
1
2
3
|
public void onLocaleChangeEvent( @Observes @Updated Locale locale) { ... } |
Спецификатор события — это обычный классификатор, определенный с помощью @Qualifier. Вот пример:
1
2
3
4
|
@Qualifier @Target ({FIELD, PARAMETER}) @Retention (RUNTIME) public @interface Updated {} |
Производители событий запускают события, используя экземпляр параметризованного интерфейса Event. Экземпляр этого интерфейса получается путем инъекции. Производитель вызывает события, вызывая метод fire () интерфейса Event, передавая объект события.
1
2
3
4
5
6
7
8
9
|
public class UserSettingsForm implements Serializable { @Inject @Any Event<Locale> localeEvent; public void localeChangeListener(ValueChangeEvent e) { // notify all observers localeEvent.fire((Locale)e.getNewValue()); } } |
1
2
|
// this will raise events to observers having parameter @Observes @Updated Locale @Inject @Updated Event<Locale> localeEvent; |
1
2
3
|
public void onLocaleChangeEvent( @Observes @Updated Locale locale, User user) { ... } |
Как насчет динамического определения спецификатора? CDI позволяет получить правильный экземпляр квалификатора с помощью AnnotationLiteral. Таким образом, мы можем передать классификатор методу select () класса Event. Пример:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
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); } } |
1
2
3
|
public void onLocaleChangeEvent( @Observes (receive = IF_EXISTS) @Updated Locale locale) { ... } |