Absrtact: Message subscription and distribution mechanism has many usage scenarios in system development, including in-process implementation (observer mode implementation, Event, Listener) and inter-system implementation (such as various message queue s). Guava provides a very lightweight implementation of EventBus in the process, which can well decouple between modules. The implementation versions of synchronization and asynchronization are also provided.
EventBus sees this name, and I'm sure you already know what it is.
Event is an event, Bus here is not the meaning of bus and car, in the field of computer generally translated into bus. EventBus then loads Event and distributes it. If you have done java swing development, you must remember that XXEvent, as well as the XXEventListener interface, will have a method onEvent(XXEvent event). Yes, EventBus does the same thing, but by using the annotations introduced by jdk1.5, it makes developing a message publishing and subscribing system very simple and convenient, without implementing interfaces or inheriting base classes.
As for the application scenario of message publishing and subscription system, it is often used in the development of server system. It is often used in the task system of game or in the activity system of some e-commerce system. For example, the game collects several props to complete a task and get rewards. Or how many days are the cumulative number of days rewarded by the check-in system on many platforms? Here we use EventBus in Guava to develop a check-in system.
public class SignInEvent { //Days of check-in private int count; public SignInEvent(int count) { this.count = count; } public int getCount() { return count; } } public class SignInProcessor { @Subscribe public void signIn(SignInEvent event) { int count = event.getCount(); //TODO Rewards Based on the Number of Days Signed in System.out.println("Sign in" + count + "day"); } } public class AppTest { @Test public void eventBusTest() { EventBus signInEventBus = new EventBus("SignInEventBus"); SignInProcessor processor = new SignInProcessor(); signInEventBus.register(processor); signInEventBus.post(new SignInEvent(2)); } }
The event handling class SignInProcessor does not need to implement an interface, just add the @Subscribe annotation to the method that needs to be processed. There is no restriction that SignInProcessor can only handle SignInEvent. To add processing logic for other events to SignInProcessor, just add a method, add annotation @Subscribe, and the first parameter is passed into the event instance that needs to be processed. .
public class SignInProcessor { @Subscribe public void signIn(SignInEvent event) { int count = event.getCount(); //TODO Rewards Based on the Number of Days Signed in System.out.println("Sign in" + count + "day"); } @Subscribe public void logout(LogoutEvent event) { //Get the logout time Date date = event.getTime(); // TODO } }
Let's start with how EventBus determines a method in a class to handle the corresponding events. An interface is provided in Guava.
interface SubscriberFindingStrategy { Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object source); }
It also provides an opportunity to annotate the implementation.
class AnnotatedSubscriberFinder implements SubscriberFindingStrategy
Realization logic of findAllSubscribers
@Override public Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object listener) { Multimap<Class<?>, EventSubscriber> methodsInListener = HashMultimap.create(); Class<?> clazz = listener.getClass(); for (Method method : getAnnotatedMethods(clazz)) { Class<?>[] parameterTypes = method.getParameterTypes(); Class<?> eventType = parameterTypes[0]; EventSubscriber subscriber = makeSubscriber(listener, method); methodsInListener.put(eventType, subscriber); } return methodsInListener; }
1. Get the listener object class and all the methods annotated by @Subscriber in the parent class, and this method has and only has one parameter.
2. Get the type of the first parameter of these methods
3. Create EventSubscriber, save listener instances and corresponding Method instances to facilitate later reflection calls to process events
After explaining how to find the processing method, how to publish the specific event object? The concrete logic here is in the post method in EventBus
public void post(Object event) { //Get all the parent classes of the event object and the interfaces implemented by the parent class //The purpose of obtaining the parent class is to publish event to some processing methods that receive the parent class Event Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass()); boolean dispatched = false; for (Class<?> eventType : dispatchTypes) { //subscribersByType is a non-thread-safe collection, so locks need to be added when operating subscribersByTypeLock.readLock().lock(); try { Set<EventSubscriber> wrappers = subscribersByType.get(eventType); if (!wrappers.isEmpty()) { dispatched = true; for (EventSubscriber wrapper : wrappers) { //Put it in the Queue corresponding to the current thread, where the ThreadLocal variable is used enqueueEvent(event, wrapper); } } } finally { subscribersByTypeLock.readLock().unlock(); } } //If the corresponding Event processor is not found and the current event type is not DeadEvent, wrap the incoming event as DeadEvent if (!dispatched && !(event instanceof DeadEvent)) { post(new DeadEvent(this, event)); } //Processing event handlers and events from the corresponding queues of the current thread dispatchQueuedEvents(); }
From the post method processing logic in EventBus, event distribution and processing are synchronized in the same thread.
However, in many cases, the processing logic of events is complex and time-consuming, and the distribution and processing of events need to be asynchronous. Event processing does not block the main thread of distribution.
Guava provides AsyncEventBus, which asynchronizes distribution and processing. The implementation of AsyncEventBus is not complicated.
AsyncEventBus inherits from EventBus. EvetsToDispatch, which stores the event queue to be distributed in EventBus, is changed from ThreadLocal < Queue < EventWithSubscriber > to Concurrent LinkedQueue < EventWithSubscriber > which supports multiple threads concurrently accessing to get event processing. The constructor of AsyncEventBus needs to pass in an Executor and can be passed into a customized thread pool according to actual needs. .
In some scenarios, event-related states need to be preserved in instances of event handling classes, and problems may arise when multithreaded concurrent access is performed. Guava provides the annotation @AllowConcurrent Events, which is used to mark whether multiple threads can simultaneously invoke the same event handler to handle dependent events.
Specific processing logic in AnnotatedSubscriberFinder class
private static EventSubscriber makeSubscriber(Object listener, Method method) { EventSubscriber wrapper; //Determine whether the event handling method is annotated by @AllowConcurrent Events if (methodIsDeclaredThreadSafe(method)) { wrapper = new EventSubscriber(listener, method); } else { //Without the @AllowConcurrent Events annotation, multiple threads need to call the processor synchronously to process events wrapper = new SynchronizedEventSubscriber(listener, method); } return wrapper; }
Well, the related use and implementation of EventBus in Guava are basically finished. It's not complicated. You need to be familiar with the following related classes and processing mechanisms
1. ThreadLocal
2. ConcurrentLinkedQueue
3. Reflect the parent class and interface of the retrieved class, which uses the TypeToken encapsulation class in Guava, and has been invoked to reflect the method.
4. ReentrantReadWriteLock
5. Cache, Guava do some encapsulation of commonly used caches, which will be covered in the next article.