Evento de guayabaBus

EvenBus es una implementación ligera del patrón Pub/Sub en Guava. En el desarrollo normal, si queremos implementar nuestro propio modelo Pub/Sub, necesitamos escribir muchas clases, el diseño también es bastante complicado y hay cierta intrusión en el código comercial, pero es muy conveniente después de usar EventBus.

En el modo Pub/Sub, hay dos implementaciones de push y pull. Por ejemplo, ActiveMQ es push, Kafka es pull y EventBus es push. EventBus también se puede considerar como una versión simple de middleware de mensajes. En lo personal siento que no hace falta separar el llamado Publisher y Subscriber, claro, no quiere decir que no haya distinción, lo principal que quiero expresar es que Publisher también puede hacer lo que hace Subscriber, y del mismo modo, el Suscriptor también puede hacer lo que hace el Publicador.
inserte la descripción de la imagen aquí

Primero mire una demostración simple para ver cómo funciona EventBus.

Conociendo la demostración

Dos oyentes:

/**
 * @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());
    }
}

Fuente:

/**
 * @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());
    }
}

resultado de la operación:inserte la descripción de la imagen aquí

En la demostración anterior, hay dos Listener, uno escucha mensajes de eventos de tipo String y el otro escucha mensajes de eventos de tipo Date. Ambos Listeners están registrados en EventBus, y un modelo Pub/Sub se implementa de manera muy simple a través de esta demostración.

Cabe señalar que el método post() de EventBus solo puede pasar un parámetro, si se requieren varios parámetros, solo se puede encapsular de acuerdo con las necesidades. Aquí está el comentario oficial de @Subscribe, que también es muy detallado:

/**
 * 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 se menciona arriba, puede echar un vistazo a esta anotación. Para decirlo sin rodeos, es para hacer que el método de los suscriptores sea seguro para subprocesos, es decir, la ejecución en serie:

/**
 * 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 {}

De hecho, para garantizar la seguridad de subprocesos es usar sincronizado, puede ver com.google.common.eventbus.Subscriber.SynchronizedSubscriber#invokeSubscriberMethod:

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

Además, EventBus no tiene un valor de retorno que no pueda obtener el resultado de la ejecución, y EventBus no proporciona un método correspondiente para obtener el resultado de la ejecución:

inserte la descripción de la imagen aquí

Evento Muerto

De hecho, el código fuente de DeadEvent es muy simple y no sé cómo usarlo en escenarios específicos. Para decirlo sin rodeos, puede ayudarlo a encapsular el evento para que pueda obtener EventBus para usted. alguna información de. La nota oficial dice esto:

/**
 * 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 es un contenedor para eventos en el método post(). Hay tal descripción en 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.

Además es muy sencillo de utilizar, eso sí, si el parámetro del método marcado por @Subscribe​ es 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 消息");
    }
}

resultado de la operación:

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

Se puede ver que DeadEvent es una capa de encapsulación de eventos. La salida predeterminada en la segunda línea del resultado en ejecución se debe a que el nombre predeterminado de EventBus es predeterminado:

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

manejo de excepciones

Qué hacer si hay una excepción en el método marcado @Subscribe, aquí hay una prueba primero:

Dos oyentes:

/**
 * @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());
    }
}

Fuente:

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());
    }
}

resultado de la operación:inserte la descripción de la imagen aquí

Vaya al método com.google.common.eventbus.Subscriber#invokeSubscriberMethod según la información de la excepción:

/**
 * 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;
  }
}

También se puede ver a partir de este método que la esencia sigue llamando al método de suscripción correspondiente a través de la reflexión. De hecho, si lo piensa más a fondo, puede ver que este método se ejecuta así:

/** 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));
          }
        }
      });
}

Cuando vea el ejecutor, tendrá una sensación brillante. Se ejecuta utilizando el grupo de subprocesos. Bueno, no puede decir eso, pero podemos pasar el Ejecutor de acuerdo con la estructura. Implementación, método de ejecución de método personalizado, de Por supuesto, también puede usar el grupo de subprocesos en JUC, que se presentará más adelante en AsyncEventBus. La excepción aquí también es manejada por try...catch​, y el método handleSubscriberException() se llama en catch​:

/** 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);
  }
}

Es natural echar un vistazo a lo que es esteExceptionHandler, porque es lo que maneja las excepciones.

Resulta ser una interfaz, que podemos pasar a través de la construcción de EventBus:

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

Esta es también una idea para hacer que el código se pueda expandir. ¿Existe la sensación de que muchos códigos fuente siguen esta rutina, y el contexto se usa aquí para encapsular algunos parámetros correspondientes?

/**
 * 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;
}

Manejo de excepciones personalizado

Como se puede expandir, expandamos uno para jugar:

/**
 * @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]);
    }
}

Pase un controlador personalizado al construir el 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());
    }
}

Resultados de la:

ExceptionListener1 recibió el mensaje: Mensaje de cadena enviado por EventBus
Manejo personalizado de excepciones...
/ por cero
Nombre del método de excepción: método de excepción
Parámetro del método de excepción: java.lang.String info
Listener2 recibió el mensaje: 2019-4-1 13:27:15

AsyncEventBus

AsyncEventBus es una subclase de EventBus. Puede adivinar por el nombre que el método de suscripción se puede ejecutar de forma asíncrona. Mire una demostración primero:

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 消息");
    }
}

resultado de la operación:

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 执行完毕

Se puede ver que, de hecho, se ejecuta de forma asíncrona. De hecho, la llamada asincronía y sincronización de AsyncEventBus y EventBus están principalmente relacionadas con el ejecutor. Desde el método post() hasta el método 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));
          }
        }
      });
}

La esencia es ejecutar a través del ejecutor, entonces, ¿qué es este ejecutor?

/** 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();
}

Es el ejecutor de EventBus en Subscriber, que es la interfaz Executor. El ejecutor en el EventBus síncrono lo proporciona Guava:

@GwtCompatible
enum DirectExecutor implements Executor {
  INSTANCE;

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

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

Para decirlo sin rodeos, es una implementación de Ejecutor muy común.

En AsyncEventBus podemos pasar el ejecutor por nosotros mismos:

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

De hecho, el diseño de este lugar no es muy bueno.La llamada sincronización y asincronía están relacionadas principalmente con el ejecutor. Si paso un ejecutor sincrónico, ¿qué debo hacer? Vamos a probarlo aquí (estoy siendo vago aquí, e implementarlo directamente con el ejecutor sincrónico en EventBus):

public class EventBusSource {

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

resultado de la operación:

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 执行完毕

Se puede encontrar que se ejecuta sincrónicamente. Este lugar también es un lugar donde el diseño no es riguroso. Se estima que el autor pretendía que pasáramos en un grupo de subprocesos. Si este es el caso, podemos bajar el nivel del ejecutor, como usar ThreadPoolExecutor.

Supongo que te gusta

Origin blog.csdn.net/qq_32907491/article/details/131502174
Recomendado
Clasificación