Статьи

Google Guava EventBus для программирования событий

Это дано в любом программном приложении, есть объекты, которые должны обмениваться информацией, чтобы выполнить работу. В приложениях Java одним из способов обмена информацией является наличие прослушивателей событий, единственной целью которых является выполнение некоторых действий при наступлении нужного события. По большей части этот процесс работает, и большинство опытных Java-разработчиков используют для написания необходимого анонимного внутреннего класса, который реализует некоторый интерфейс прослушивателя событий. Этот пост посвящен другому подходу к обработке событий Java с использованием EventBus в Guava. EventBus позволяет объектам подписываться или публиковать события, не имея явных знаний друг о друге. EventBus не предназначен для использования в качестве системы публикации / подписки общего назначения или для поддержки межпроцессного взаимодействия.

EventBus Class

EventBus очень гибок и может использоваться как одиночный, или приложение может иметь несколько экземпляров для размещения передачи событий в разных контекстах. EventBus будет отправлять все события последовательно, поэтому важно, чтобы методы обработки событий были легкими. Если вам нужно выполнить более сложную обработку в обработчиках событий, есть другой вариант EventBus, AsyncEventBus. Функция AsyncEventBus идентична по функциональности, но принимает ExecutorService в качестве аргумента конструктора, чтобы обеспечить асинхронную диспетчеризацию событий.

Подписка на события

Объект подписывается на события, выполняя следующие действия:

  1. Определите открытый метод, который принимает один аргумент желаемого типа события, и поместите аннотацию @Subscribe для этого метода.
  2. Зарегистрируйтесь в EventBus, передав экземпляр объекта методу EventBus.register.

Вот краткий пример с деталями, опущенными для ясности:

01
02
03
04
05
06
07
08
09
10
public class PurchaseSubscriber {
   @Subscribe
   public void handlePurchaseEvent(PurchaseEvent event) {
      .....
   }
  .....
 
 EventBus eventBus = new EventBus();
 PurchaseSubscriber purchaseSubscriber = new PurchaseSubscriber();
 eventBus.register(purchaseSubscriber);

Есть еще одна аннотация, которую можно использовать вместе с @Subscribe, и это @AllowConcurrentEvents. @AllowConcurrentEvents помечает метод-обработчик как потокобезопасный, поэтому EventBus (скорее всего, AsyncEventBus) потенциально может вызывать обработчик событий из одновременных потоков. Одна интересная вещь, которую я обнаружил в модульном тестировании, заключается в том, что если метод-обработчик не имеет аннотации @AllowConcurrentEvents, он будет вызывать обработчики для события последовательно, даже при использовании AsyncEventBus. Важно отметить, что @AllowConcurrentEvents не будет помечать метод как обработчик событий, аннотация @Subscribe все еще должна присутствовать. Наконец, метод обработки событий должен иметь один и только один параметр, иначе IllegalArgumentException будет выброшен при регистрации объекта в EventBus.

Издательские события

Публикация событий с помощью EventBus также проста. В разделе кода, куда вы хотите отправить уведомление о событии, вызовите EventBus.post, и все подписчики, зарегистрированные для этого объекта события, будут уведомлены.

1
2
3
4
5
public void handleTransaction(){
  purchaseService.purchase(item,amount);
  eventBus.post(new CashPurchaseEvent(item,amount));
  ....
}

Хотя это может быть очевидно, важно, чтобы подписывающиеся и публикующие классы совместно использовали один и тот же экземпляр EventBus, и было бы целесообразно использовать Guice или Spring для управления зависимостями.

Подробнее о обработчиках событий

Очень мощная особенность EventBus заключается в том, что вы можете сделать ваши обработчики такими же точными или точными, как необходимо. EventBus будет вызывать зарегистрированных подписчиков для всех подтипов и реализованных интерфейсов размещенного объекта события. Например, для обработки любых событий можно создать обработчик событий, который принимает параметр типа Object. Чтобы обрабатывать только единичные события, создайте обработчик, который очень специфичен для типа. Чтобы проиллюстрировать это, рассмотрим следующую простую иерархию событий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public abstract class PurchaseEvent {
        String item;
   public PurchaseEvent(String item){
       this.item = item;
   }
}
 
public class CashPurchaseEvent extends PurchaseEvent {
       int amount;
       public CashPurchaseEvent(String item, int amount){
          super(item);
          this.amount = amount;
       }
}
 
public class CreditPurchaseEvent extends PurchaseEvent {
       int amount;
       String cardNumber;
       public CreditPurchaseEvent(String item,int amount, String cardNumber){
          super(item);
          this.amount = amount;
          this.cardNumber = cardNumber;
       }
}

Вот соответствующие классы обработки событий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
//Would only be notified of Cash purchase events
 public class CashPurchaseSubscriber {
     @Subscribe
     public void handleCashPurchase(CashPurchaseEvent event){
      ...
     }
 }
 //Would only be notified of credit purchases
 public class CreditPurchaseSubscriber {
    @Subscribe
    public void handleCreditPurchase(CreditPurchaseEvent event) {
     ....
    }
}
//Notified of any purchase event
 public class PurchaseSubscriber {
    @Subscribe
    public void handlePurchaseEvent(PurchaseEvent event) {
       .....
    }
}

Если необходимо захватить широкий диапазон типов событий, альтернативой может быть наличие более одного метода обработки событий в классе. Наличие нескольких обработчиков может быть лучшим решением, так как вам не придется выполнять какие-либо проверки «instanceof» для параметра объекта события. Вот простой пример из моего модульного теста:

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
public class MultiHandlerSubscriber {
 
    List<CashPurchaseEvent> cashEvents = new ArrayList<ashPurchaseEvent>();
    List<CreditPurchaseEvent> creditEvents = new ArrayList<CreditPurchaseEvent>();
    List<SimpleEvent> simpleEvents = new ArrayList<SimpleEvent>();
 
    public MultiHandlerSubscriber(EventBus eventBus){
        eventBus.register(this);
    }
 
    @Subscribe
    public void handleCashEvents(CashPurchaseEvent event){
        cashEvents.add(event);
    }
 
    @Subscribe
    public void handleCreditEvents(CreditPurchaseEvent event){
        creditEvents.add(event);
    }
 
    @Subscribe
    public void handleSimpleEvents(SimpleEvent event){
        simpleEvents.add(event);
    }
....

тестирование

Поскольку обработчики событий являются просто простыми методами, их можно легко протестировать, создав экземпляр EventBus в тестовом примере или смоделировав EventBus, передав соответствующий объект события. При работе с EventBus я обнаружил, что очень легко:

  • Забудьте зарегистрировать подписывающийся объект в EventBus
  • Не забудьте добавить аннотацию @Subscribe.

Если кажется, что обработчик событий не вызывается, сначала проверьте эти две ошибки.
Полезной техникой отладки является подписка на класс DeadEvent. EventBus обернет любое опубликованное событие без обработчиков в экземпляре DeadEvent. DeadEvent предоставляет метод getEvent, который возвращает исходный объект события.

Вывод

Класс Guava EventBus предоставляет привлекательную и полезную альтернативу стандартному механизму обработки событий Java. Есть надежда, что читатель найдет EventBus таким же полезным, как и я. Как всегда комментарии и предложения приветствуются.

Ресурсы

Ссылка: Программирование событий с помощью Google Guava EventBus от нашего партнера по JCG Билла Бекака в блоге Randomечанище по кодированию .