EventBus Гуавы

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

В режиме Pub/Sub есть две реализации push и pull. Например, ActiveMQ — push, Kafka — pull и EventBus — push. EventBus также можно рассматривать как простую версию промежуточного ПО для сообщений. Лично я считаю, что т.н. Publisher и Subscriber не нужно разделять, конечно, это не значит, что нет различия.Главное, что я хочу выразить, это то, что Publisher также может делать то, что делает Точно так же подписчик может делать то же, что и издатель.
вставьте сюда описание изображения

Сначала посмотрите простую демонстрацию, чтобы увидеть, как работает EventBus.

Знакомство с демонстрацией

Два слушателя:

/**
 * @author dongguabai
 * @date 2019-03-18 18:09
 * Listener1
 */
public class Listener1 {

    @Subscribe  //监听 参数为 String 的消息
    public void doSth(String info) {
        System.out.println("Listener1 接收到了消息:" + info);
    }
}
/**
 * @author dongguabai
 * @date 2019-03-18 18:09
 * Listener2
 */
public class Listener2 {

    @Subscribe  //监听 参数为 Date 的消息
    public void doSth(Date info) {
        System.out.println("Listener2 接收到了消息:" + info.toLocaleString());
    }
}

Источник:

/**
 * @author dongguabai
 * @date 2019-03-18 18:15
 */
public class EventBusSource {

    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        eventBus.register(new Listener1());
        eventBus.register(new Listener2());
        eventBus.post("EventBus 发送的 String 消息");
        eventBus.post(new Date());
    }
}

результат операции:вставьте сюда описание изображения

В приведенной выше демонстрации есть два прослушивателя, один прослушивает сообщения о событиях типа String, а другой прослушивает сообщения о событиях типа Date. Оба этих слушателя зарегистрированы в EventBus, а модель Pub/Sub реализуется очень просто с помощью этой демонстрации.

Следует отметить, что метод post() EventBus может передавать только один параметр.Если требуется несколько параметров, он может быть инкапсулирован только в соответствии с потребностями. Вот официальный комментарий @Subscribe, который также очень подробный:

/**
 * Marks a method as an event subscriber.
 *
 * <p>The type of event will be indicated by the method's first (and only) parameter. If this annotation is applied to methods with zero parameters, or more than one parameter, the object containing the method will not be able to register for event delivery from the {@link EventBus}.
 *
 * <p>Unless also annotated with @{@link AllowConcurrentEvents}, event subscriber methods will be invoked serially by each event bus that they are registered with.
 *
 * @author Cliff Biffle
 * @since 10.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Beta
public @interface Subscribe {}

@AllowConcurrentEvents упомянут выше, можете взглянуть на эту аннотацию, грубо говоря, для того, чтобы сделать метод Subscribers потокобезопасным, то есть серийным выполнением:

/**
 * Marks an event subscriber method as being thread-safe. This annotation indicates that EventBus
 * may invoke the event subscriber simultaneously from multiple threads.
 *
 * <p>This does not mark the method, and so should be used in combination with {@link Subscribe}.
 *
 * @author Cliff Biffle
 * @since 10.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Beta
public @interface AllowConcurrentEvents {}

На самом деле, для обеспечения безопасности потоков используется синхронизированный​, вы можете увидеть com.google.common.eventbus.Subscriber.SynchronizedSubscriber#invokeSubscriberMethod​​:

@Override
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
  synchronized (this) {
    super.invokeSubscriberMethod(event);
  }
}

Более того, EventBus не имеет возвращаемого значения, которое не может получить результат выполнения, а EventBus не предоставляет соответствующего метода для получения результата выполнения:

вставьте сюда описание изображения

DeadEvent

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

/**
 * Wraps an event that was posted, but which had no subscribers and thus could not be delivered.
 *
 * <p>Registering a DeadEvent subscriber is useful for debugging or logging, as it can detect misconfigurations in a system's event distribution.
 *
 * @author Cliff Biffle
 * @since 10.0
 */
@Beta
public class DeadEvent {

  private final Object source;
  private final Object event;
  ...
  ...
}

DeadEvent — это оболочка для события в методе post(). В com.google.common.eventbus.EventBus#post есть такое описание:

* <p>If no subscribers have been subscribed for {@code event}'s class, and {@code event} is * not already a {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted.

Также он очень прост в использовании, то есть если параметр метода, помеченный @Subscribe​, имеет значение DeadEvent​.

public class DeadEventListener {

    @Subscribe
    public void deadEventMethod(DeadEvent deadEvent){
        System.out.println("DeadEvent_Source_Class:"+deadEvent.getSource().getClass());
        System.out.println("DeadEvent_Source:"+deadEvent.getSource());
        System.out.println("DeadEvent_Event:"+deadEvent.getEvent());
    }
}
public class DeadEventEventBusSource {

    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        eventBus.register(new DeadEventListener());
        eventBus.post("EventBus 发送的 String 消息");
    }
}

результат операции:

DeadEvent_Source_Class:class com.google.common.eventbus.EventBus
DeadEvent_Source:EventBus{default}
DeadEvent_Event:EventBus 发送的 String 消息

Видно, что DeadEvent — это слой инкапсуляции события. Вывод по умолчанию во второй строке текущего результата связан с тем, что имя EventBus по умолчанию — default:

/** Creates a new EventBus named "default". */
public EventBus() {
  this("default");
}

Обработка исключений

Что делать, если в помеченном методе @Subscribe есть исключение, вот сначала тест:

Два слушателя:

/**
 * @author dongguabai
 * @date 2019-03-18 18:09
 * ExceptionListener1
 */
public class ExceptionListener1 {

    @Subscribe  //监听 参数为 String 的消息
    public void exceptionMethod(String info) {
        System.out.println("ExceptionListener1 接收到了消息:" + info);
        int i = 1/0;
    }
}
/**
 * @author dongguabai
 * @date 2019-03-18 18:09
 * Listener2
 */
public class Listener2 {

    @Subscribe  //监听 参数为 Date 的消息
    public void doSth(Date info) {
        System.out.println("Listener2 接收到了消息:" + info.toLocaleString());
    }
}

Источник:

public class ExceptionEventBusSource {

    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        eventBus.register(new ExceptionListener1());
        eventBus.register(new Listener2());
        eventBus.post("EventBus 发送的 String 消息");
        eventBus.post(new Date());
    }
}

результат операции:вставьте сюда описание изображения

Перейдите к методу com.google.common.eventbus.Subscriber#invokeSubscriberMethod в соответствии с информацией об исключении:

/**
 * Invokes the subscriber method. This method can be overridden to make the invocation
 * synchronized.
 */
@VisibleForTesting
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
  try {
    method.invoke(target, checkNotNull(event));
  } catch (IllegalArgumentException e) {
    throw new Error("Method rejected target/argument: " + event, e);
  } catch (IllegalAccessException e) {
    throw new Error("Method became inaccessible: " + event, e);
  } catch (InvocationTargetException e) {
    if (e.getCause() instanceof Error) {
      throw (Error) e.getCause();
    }
    throw e;
  }
}

Также из этого метода видно, что суть по-прежнему вызывает соответствующий метод subscribe через рефлексию. На самом деле, если подумать дальше, то можно увидеть, что этот метод выполняется так:

/** Dispatches {@code event} to this subscriber using the proper executor. */
final void dispatchEvent(final Object event) {
  executor.execute(
      new Runnable() {
        @Override
        public void run() {
          try {
            invokeSubscriberMethod(event);
          } catch (InvocationTargetException e) {
            bus.handleSubscriberException(e.getCause(), context(event));
          }
        }
      });
}

Когда вы увидите экзекьютор, у вас возникнет светлое чувство.Он выполняется с помощью пула потоков.Ну так нельзя сказать,но мы можем пройти в экзекьютор по структуре.Реализация,метод выполнения пользовательского метода,из Конечно, вы также можете использовать пул потоков в JUC, который будет представлен в AsyncEventBus. Исключение здесь также обрабатывается с помощью try...catch​, а в catch​ вызывается метод handleSubscriberException():

/** Handles the given exception thrown by a subscriber with the given context. */
void handleSubscriberException(Throwable e, SubscriberExceptionContext context) {
  checkNotNull(e);
  checkNotNull(context);
  try {
    exceptionHandler.handleException(e, context);
  } catch (Throwable e2) {
    // if the handler threw an exception... well, just log it
    logger.log(
        Level.SEVERE,
        String.format(Locale.ROOT, "Exception %s thrown while handling exception: %s", e2, e),
        e2);
  }
}

Естественно взглянуть на то, что представляет собой этот exceptionHandler, потому что именно он обрабатывает исключения.

Получается интерфейс, в который мы можем передать через конструкцию EventBus:

public EventBus(SubscriberExceptionHandler exceptionHandler) {
  this(
      "default",
      MoreExecutors.directExecutor(),
      Dispatcher.perThreadDispatchQueue(),
      exceptionHandler);
}

Это также идея сделать код расширяемым, есть ли ощущение, что многие исходники следуют этой процедуре, и здесь используется контекст для инкапсуляции некоторых соответствующих параметров:

/**
 * Context for an exception thrown by a subscriber.
 *
 * @since 16.0
 */
public class SubscriberExceptionContext {
  private final EventBus eventBus;
  private final Object event;
  private final Object subscriber;
  private final Method subscriberMethod;
}

Пользовательская обработка исключений

Поскольку его можно расширить, давайте расширим его, чтобы играть:

/**
 * @author dongguabai
 * @date 2019-04-01 13:21
 * 自定义异常处理
 */
public class ExceptionHandler implements SubscriberExceptionHandler {
    @Override
    public void handleException(Throwable exception, SubscriberExceptionContext context) {
        System.out.println("自定义异常处理....");
        System.out.println(exception.getLocalizedMessage());
        System.out.println("异常方法名称:"+context.getSubscriberMethod().getName());
        System.out.println("异常方法参数:"+context.getSubscriberMethod().getParameters()[0]);
    }
}

Передайте пользовательский обработчик при создании EventBus:

public class ExceptionEventBusSource {

    public static void main(String[] args) {
        //传入自定义异常处理 Handler
        EventBus eventBus = new EventBus(new ExceptionHandler());
        eventBus.register(new ExceptionListener1());
        eventBus.register(new Listener2());
        eventBus.post("EventBus 发送的 String 消息");
        eventBus.post(new Date());
    }
}

Результаты:

ExceptionListener1 получил сообщение: Строковое сообщение, отправленное EventBus
Пользовательская обработка исключений...
/ by zero
Имя метода исключения: exceptionMethod
Параметр метода исключения: java.lang.String info
Listener2 получил сообщение: 2019-4-1 13:27:15

AsyncEventBus

AsyncEventBus является подклассом EventBus. Из названия можно догадаться, что метод subscribe может выполняться асинхронно. Сначала посмотрите демо:

public class Listener1 {

    @Subscribe  //监听 参数为 String 的消息
    public void doSth0(String info) {
        System.out.println(LocalDateTime.now()+"   方法0 执行完毕");
    }



    @Subscribe  //监听 参数为 String 的消息
    public void doSth1(String info) {
        System.out.println(LocalDateTime.now()+"   方法1 执行开始");

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(LocalDateTime.now()+"   方法1 执行完毕");
    }

    @Subscribe  //监听 参数为 String 的消息
    public void doSth2(String info) {
        System.out.println(LocalDateTime.now()+"   方法2 执行开始");

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(LocalDateTime.now()+"   方法2 执行完毕");
    }

    @Subscribe  //监听 参数为 String 的消息
    public void doSth3(String info) {
        System.out.println(LocalDateTime.now()+"   方法3 执行开始");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(LocalDateTime.now()+"   方法3 执行完毕");
    }

    @Subscribe  //监听 参数为 String 的消息
    public void doSth4(String info) {
        System.out.println(LocalDateTime.now()+"   方法4 执行开始");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(LocalDateTime.now()+"   方法4 执行完毕");
    }
}
public class EventBusSource {

    public static void main(String[] args) {
        EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(10));
        eventBus.register(new Listener1());
        eventBus.post("EventBus 发送的 String 消息");
    }
}

результат операции:

2019-04-01T16:35:56.223   方法3 执行开始
2019-04-01T16:35:56.223   方法0 执行完毕
2019-04-01T16:35:56.223   方法1 执行开始
2019-04-01T16:35:56.223   方法4 执行开始
2019-04-01T16:35:56.223   方法2 执行开始
2019-04-01T16:35:57.226   方法1 执行完毕
2019-04-01T16:35:58.226   方法2 执行完毕
2019-04-01T16:35:59.224   方法3 执行完毕
2019-04-01T16:36:00.225   方法4 执行完毕

Видно, что он действительно выполняется асинхронно. На самом деле так называемая асинхронность и синхронизация AsyncEventBus и EventBus в основном связаны с исполнителем. От метода post() до метода com.google.common.eventbus.Subscriber#dispatchEvent:

/** Dispatches {@code event} to this subscriber using the proper executor. */
final void dispatchEvent(final Object event) {
  executor.execute(
      new Runnable() {
        @Override
        public void run() {
          try {
            invokeSubscriberMethod(event);
          } catch (InvocationTargetException e) {
            bus.handleSubscriberException(e.getCause(), context(event));
          }
        }
      });
}

Суть в том, чтобы выполнить через экзекьютор, так что же это за экзекьютор:

/** Executor to use for dispatching events to this subscriber. */
private final Executor executor;

private Subscriber(EventBus bus, Object target, Method method) {
  this.bus = bus;
  this.target = checkNotNull(target);
  this.method = method;
  method.setAccessible(true);

  this.executor = bus.executor();
}

Это исполнитель EventBus в Subscriber, который является интерфейсом Executor. Исполнитель в синхронном EventBus предоставляется самой Guava:

@GwtCompatible
enum DirectExecutor implements Executor {
  INSTANCE;

  @Override
  public void execute(Runnable command) {
    command.run();
  }

  @Override
  public String toString() {
    return "MoreExecutors.directExecutor()";
  }
}

Грубо говоря, это очень распространенная реализация Executor.

В AsyncEventBus мы можем сами передать executor:

public AsyncEventBus(Executor executor) {
  super("default", executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
}

На самом деле, дизайн этого места не очень хорош.Я также проанализировал, что так называемая синхронизация и асинхронность в основном связаны с исполнителем. Если я передам синхронный экзекьютор, что мне делать?Проверим здесь (я тут ленюсь, и реализую напрямую с синхронным экзекьютором в EventBus):

public class EventBusSource {

    public static void main(String[] args) {
        AsyncEventBus eventBus = new AsyncEventBus(MoreExecutors.directExecutor());
        eventBus.register(new Listener1());
        eventBus.post("EventBus 发送的 String 消息");
    }
}

результат операции:

2019-04-01T17:13:23.158   方法4 执行开始
2019-04-01T17:13:27.162   方法4 执行完毕
2019-04-01T17:13:27.162   方法0 执行完毕
2019-04-01T17:13:27.162   方法1 执行开始
2019-04-01T17:13:28.166   方法1 执行完毕
2019-04-01T17:13:28.166   方法2 执行开始
2019-04-01T17:13:30.171   方法2 执行完毕
2019-04-01T17:13:30.171   方法3 执行开始
2019-04-01T17:13:33.175   方法3 执行完毕

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

Guess you like

Origin blog.csdn.net/qq_32907491/article/details/131502174