Google-Guava-EventBus source code explanation, guavaeventbus

Source: Internet
Author: User
Tags google guava eventbus

Google-Guava-EventBus source code explanation, guavaeventbus

Guava is a Java basic class library open-source by Google. It is widely used within Google. Guava provides many functional modules, such as collections, concurrent libraries, and caches. EventBus is one of the modules. This article uses the EventBus source code to discuss its design and implementation.

Summary

First, let's preview all the class diagrams of the EventBus module:


There are not many classes and there are almost no inheritance relationships.

Next, let's take a look at the responsibilities of each category:

  • EventBus: a core class that represents an event bus. The Publish event is also initiated by it.
  • AsyncEventBus: pushes events to the asynchronous distribution mode of a global queue.
  • Subscriber: It abstracts the processor of an event, encapsulates the Subscriber and processor of the event, and is responsible for event processing (the class name and its semantics of this class are somewhat ambiguous, which will be discussed later ).
  • SubscriberRegistry: Subscriber. It is used to store the correspondence between Subscriber and Event, so that EventBus can find its Subscriber when publish an Event.
  • Dispatcher: The Event distributor, which defines the event distribution policy.
  • @ Subscribe: annotation used to identify the event processor. When EventBus publish is an event, the corresponding Subscriber will be notified and execute the event processor.
  • @ AllowConcurrentEvents: This annotation is used with @ Subscribe to identify the subscriber's processing method as thread-safe. This annotation is also used to identify the method and may be executed by EventBus in a multi-thread environment.
  • DeadEvent: the object of a dead message (event not followed by a subscriber.
  • SubscribeExceptionHandler: The processor that the subscriber throws an exception.
  • SubscribeExceptionContext: context object in which the subscriber throws an exception.
Before decomposing each class, let's take a look at the associations between classes:

To interpret EventBus by "class", it has the following fields:
  • Identifier: the identifier of the event bus. This indicates that multiple EventBus can exist in an application. If it is not specified, it uses "default" as its default name.
  • Executor: it is an instance of the Executor interface and is used to execute the subscriber's event processing method. It should be noted that the field is instantiated in the EventBus internal constructor and is not injected from the outside. In addition, the real execution of the subscriber method is not the responsibility of EventBus, the Subscriber is responsible for this field, so it will be exposed to external access.
  • Predictionhandler: it is an instance of SubscribeExceptionHandler and is used to handle exceptions thrown by subscriber when performing event processing methods. EventBus can receive an externally defined exception processor or use an internal default logging processor.
  • Subscribers: subscriber registry, used to store all events, event processors, and subscription objects.
  • Dispatcher: The Event distributor used to distribute events to the event processor of the subscription object. This object is initialized within the EventBus constructor. The default implementation is PerThreadQueuedDispatcher, which stores events in the queue, it also ensures that events sent on the same thread can be distributed to all subscribers in the order they are published.
EventBus provides several core methods:
  • Register: register subscriber;
  • Unregister: remove the registered subscriber;
  • Post: publish events;

You can regard EventBus as a proxy. The real implementers of these methods are the objects above.

AsyncEventBus is an EventBus that supports asynchronous publishing mode. It overwrites the Default Construction Method of EventBus and specifies an asynchronous distributor: LegacyAsyncDispatcher, this distributor stores unpublished events based on a global queue.
As mentioned earlier, the name of Subscriber is confusing. The name of this class seems to be a subscriber object, but it is actually used to encapsulate an "event processor of a subscriber" object. Because when a Subscriber has multiple Processing Methods marked as @ Subscribe, each processing method corresponds to an instance of an independent Subscriber object. I personally think this name is somewhat confused with its specific implementation semantics. Of course, the implementers may think that if an object and an event processor are a Subscriber, there is no problem. Therefore, for ease of understanding, you can regard it as an entity class that encapsulates the subscriber object and a subscriber processor method.
The access level of Subscriber is package, and it is also responsible for executing event processing. Use a create static factory method to create it:
static Subscriber create(EventBus bus, Object listener, Method method) {    return isDeclaredThreadSafe(method)        ? new Subscriber(bus, listener, method)        : new SynchronizedSubscriber(bus, listener, method);  }

It receives three parameters:
  • Bus: EventBus instance. It is used to obtain the executor of an event)
  • Listener: Real subscriber object
  • Method: The Method Instance of the event processing method of the subscription object.
In implementation, it first checks whether the processor method is labeled with @ AllowConcurrentEvents annotations. If yes, an instance of the Subscriber class is instantiated. If no, eventbus is not allowed to call the processor method in a multi-threaded environment. Therefore, a synchronous subscriber object, SynchronizedSubscriber, is provided here to ensure thread security.
One of the two key methods of this class:
DispatchEvent:
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));        }      }    });  }

It calls a multi-threaded executor to execute the event processor method.
Another method: invokeSubscriberMethod calls the event processor method in reflection mode.
In addition, this class override the Object's equals method and marks it as final. It is mainly used to prevent the same object from repeatedly subscribing to an event. In SubscriberRegistry, operations such as determination will be performed accordingly. Here, the Subscriber also override and final the hashCode method. This is the best practice. You don't have to talk about it. If you don't know anything about it, you can go and look at objective Java.
This class also has an internal class, which is the SynchronizedSubscriber we mentioned above. It inherits the Subscriber. The only difference from Subscriber is that it synchronizes the execution of invokeSubscriberMethod.
SubscriberRegistry maintains the relationship between subscriptions and events of a single EventBus. The object used internally to store the Subscriber relationship is the concurrent Map: ConcurrentMap under the java concurrency package. This map uses the Class object as the key, and the value type is CopyOnWriteArraySet <Subscriber> set type.
The SubscriberRegistry directly depends on the EventBus object. Therefore, the EventBus instance needs to be injected into the constructor.
SubscriberRegistry has two key instance methods: register/unregister. Register receives the Subscriber object as a parameter and establishes the association between the Event and Subscriber.
Let's take a look at its implementation:
void register(Object listener) {    Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);    for (Map.Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {      Class<?> eventType = entry.getKey();      Collection<Subscriber> eventMethodsInListener = entry.getValue();      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);      if (eventSubscribers == null) {        CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<Subscriber>();        eventSubscribers = MoreObjects.firstNonNull(            subscribers.putIfAbsent(eventType, newSet), newSet);      }      eventSubscribers.addAll(eventMethodsInListener);    }  }

It first obtains a Multimap instance (it is a multi-value Map type provided by the Google Guava Collection framework, that is, a key can correspond to multiple values ), the Multimap is used to store all the set of processor methods for the event in the subscriber corresponding to the event type, and its key is the Class type of the event. Here, the map view is obtained through asMap in the for loop, and multiple values corresponding to Multimap can be stored in a Collection.
That is to say, each entry in the for loop indicates a set of Subscriber corresponding to an event Class instance, that is, eventMethodsInListener.
Obtain the corresponding set of storage Subscriber instances from the registry based on the Class Object of the event. If the set does not exist, create the set, add all the event processor methods in the subscriber to the Registry.
The implementation of unregisterunregister is similar to that of register. First, find the correspondence between all event types of the subscriber and the processor. Then, traverse all event types and remove all Subscriber instances for the current Subscriber.
The findAllSubscribersregister/unregister Methods call the findAllSubscribers method, which has some special features. You need to extract them here.
FindAllSubscribers is used to find the event type and event processor ing relationship. Reflection is required to search for annotations. Reflection is used to obtain Annotations on methods. Guava uses an implicit contract for EventBus registration, instead of an explicit contract such as an interface ". Classes and interfaces have an inheritance relationship. It is very likely that a subscriber's parent class (or an interface implemented by the parent class) also subscribes to an event. Therefore, the search here needs to follow the inheritance chain up to find whether the method of the parent class is also annotated, the code implementation:
  private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {    Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();    Class<?> clazz = listener.getClass();    for (Method method : getAnnotatedMethods(clazz)) {      Class<?>[] parameterTypes = method.getParameterTypes();      Class<?> eventType = parameterTypes[0];      methodsInListener.put(eventType, Subscriber.create(bus, listener, method));    }    return methodsInListener;  }

In addition, the method for obtaining a Subscriber instance based on the event type is getSubscribers.
GetSubscribers
  Iterator<Subscriber> getSubscribers(Object event) {    ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass());    List<Iterator<Subscriber>> subscriberIterators =        Lists.newArrayListWithCapacity(eventTypes.size());    for (Class<?> eventType : eventTypes) {      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);      if (eventSubscribers != null) {        // eager no-copy snapshot        subscriberIterators.add(eventSubscribers.iterator());      }    }    return Iterators.concat(subscriberIterators.iterator());  }

Dispatcherdispatcher is used to distribute events to Subscriber. It implements multiple schedulers internally to provide different event sequences in different scenarios. Dispatcher is an abstract class that defines a core abstract method:

abstract void dispatch(Object event, Iterator<Subscriber> subscribers);

This method is used to distribute a specified event to all subscribers.

In addition, the Dispatcher provides three different distributor implementations:

PerThreadQueuedDispatcher

It is usually used to create a queue for each thread to store event objects. Ensure that all events are sent from a single thread in their publish order. Ensure that a queue is sent from a single thread. It is mainly defined internally and put in ThreadLocal to be associated with a specific thread.

LegacyAsyncDispatcher is another asynchronous distributor implementation: LegacyAsyncDispatcher, which was previously mentioned in AsyncEventBus to distribute events.
It stores events internally through a global queue of concurrentincluqueue <EventWithSubscriber>. From the key method: the implementation of dispatch, the difference between it and PerThreadQueuedDispatcher is mainly the difference in two loops (here, the queue-based Cache event method will certainly have two loops: cyclically fetch events in the queue and send them to Subscriber in a loop ).
PerThreadQueuedDispatcher: it is a two-layer nested loop. The outer layer is the event retrieved from the traversal queue, and the memory is the subscription processor of the traversal event.
LegacyAsyncDispatcher: the first and second cycles. The first one is to traverse the event subscription processor and construct an event entity object to be saved to the queue. The next loop is to traverse the event entity object queue and retrieve the events in the event entity object for distribution.
ImmediateDispatcher can both be seen as an asynchronous mode based on the intermediate queue, while ImmediateDispatcher is a synchronous mode: once an event occurs, it is immediately distributed and processed. ImmediateDispatcher is visually similar to linear and sequential execution. In queue mode, multiple threads are used to aggregate a public queue from a divergence to an aggregation model. Therefore, the distribution method of ImmediateDispatcher is depth-first, while queue-used is breadth-first.
DeadEvent is an object that encapsulates events without subscribers. DeadEvent consists of two attributes:
  • Source: Event source (usually the EventBus object that publishes an event)
  • Event: event object
DeadEvent object generation: when an event is published through an EventBus instance, the event subscriber is not found and it is not a DeadEvent instance, an instance of the DeadEvent class will be built by EventBus.
To sum up the EventBus source code of Guava, it is relatively simple and clear. From the source code point of view, it is a common Observer design method, give up using a unified interface, unified event object type. Instead, it adopts the annotation-based scanning binding method.
In fact, both the forced implementation of unified interfaces and the implementation method based on annotations are building an association (or meeting a certain contract ). Obviously, the interface method is the explicit contract forced at the compilation level, while the annotation method is the implicit contractual relationship dynamically bound at runtime. The interface is a traditional method. The observer relationship is determined during compilation, which is clear and clear. However, consistent event types and method signatures are usually required. The annotation-based implementation mechanism, on the contrary, is not so clear during compilation because there is no interface dependency on the syntax layer. At least it is difficult for static analysis tools to show the observer relationship, however, there is no need for consistent method signatures and event parameters. As for the inheritance relationship between multiple subscriber classes, you can inherit the notifications that receive events, which is both an advantage and a disadvantage.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.