Spring event pattern application

Keywords: Spring Java Database REST

I'm the interviewer of Microsoft YaHei: it's 85 years old
Me: Well, 35
Interviewer: that should be very experienced. Let's talk about spring
Me: OK, I've used this for more than 10 years. You can ask me whatever you like
Interviewer: have you ever used events in Spring?
Me: Yes
Interviewer: can you explain why you need to use events?
I: the event mode can be used to decouple the system. The event source publishes an event, and the event listener can consume this event. The event source does not need to pay attention to which listeners are published, which can decouple the system
Interviewer: how many ways can Spring events be implemented?
Me: Generally speaking, there are two ways. The first way is through the interface, and the second way is to use annotations on methods
Interviewer: is event listener processing synchronous or asynchronous in Spring?
Me: sorry, I didn't understand
Interviewer: is event publishing and event listener execution running in the same thread?
Me: executing in a thread is the way of synchronization
Interviewer: do you support asynchronous mode?
Me: support
Interviewer: are you sure?
Me: Um... , this is not used, but I think it is OK. The logic in the event listener is not the main business and can no longer be executed in the current thread.
Interviewer: does event listener support custom order in spring?
Me: I don't know
Interviewer: OK, that's all for today's interview. After you go back, consolidate your technology and look at the source code more often. Don't waste it, or it will become more and more difficult
Me: OK.... At this time, the brain is already mushy.

After going back, I quickly turned out the spring event source code and studied it several times.

Key questions in the interview process

1. Why do you need to use the event mode?
2. How to implement events in spring?
3. Does event listener consume events in spring support asynchronous mode?
4. Does event listener consume events in spring support custom order?

Let's introduce them one by one.

Why do you need to use time?
Let's start with a business scenario:

Product Manager: passerby, you can help me realize a registration function in these two days

Me: the registration function is relatively simple. Just put the user information into the database. The pseudo code is as follows:

public void registerUser(UserModel user){
    //Insert user information into db and complete registration
    this.insertUser(user);
}

After a few days, product manager: passerby, after successful registration, send a successful email to the user

Me: modify the above registration code as follows:

public void registerUser(UserModel user){
    //Insert user information into db and complete registration
    this.insertUser(user);
    //Send mail
    this.sendEmailToUser(user);
}

Due to the modification of the registration interface, all the places that call this method need to be retested, and the test brothers help to run it again.

A few days later, product manager: passerby, after successful registration, send coupons to users

Me: OK, I adjusted the code again

public void registerUser(UserModel user){
    //Insert user information into db and complete registration
    this.insertUser(user);
    //Send mail
    this.sendEmailToUser(user);
    //Send coupons
    this.sendCouponToUser(user);
}

Me: Test brothers, work hard for you. The registration interface has been modified again. Help me to go over it again.

After a period of time, the company's benefit is too good. Product Manager: passerby, when registering, cancel the function of sending coupons to users.

Me: I ran to adjust the above code again and killed the function of sending coupons, as follows

public void registerUser(UserModel user){
    //Insert user information into db and complete registration
    this.insertUser(user);
    //Send mail
    this.sendEmailToUser(user);
}

Because the code has been adjusted, and the registration function belongs to the core business, you need to let the test help you again, and you need to bother the test again.

Suddenly one day, product manager: passerby, why is the registration interface so slow and often fails? How many users do you want the company to lose

Me: I ran to check the running log quickly, and found that it was unstable to send email to users when registering, and it was dependent on the third-party email server, which took a long time and was prone to failure.

Run to the product manager and say: due to the unstable mail server, the registration is not stable.

Product Manager: you can not send email, but you have to make sure the registration function can be used.

After thinking about it, I changed the above code to the following, and put the sending mail into the sub thread to execute:

public void registerUser(UserModel user){
    //Insert user information into db and complete registration
    this.insertUser(user);
    //Send e-mail and execute it in a sub thread. The sending result of e-mail will not interfere with the registration logic
    new Thread(()->{
        this.sendEmailToUser(user);
    }).start();
}

A few days later, the product manager came again and said: passers by, the recent benefits are not good, need to stimulate user consumption, continue to send coupons when registering.

Me: Well, this is to play with me. I have to adjust the registered code repeatedly to make it better. I have to do tests repeatedly. This is to kill us.

It took some time to do a good job of reorganizing: it was found that the problem was not the product manager. From the business point of view, these requirements raised by the product were reasonable, but the repeated adjustment of the result code, repeated testing, and some minor functions led to the instability of the registration interface. In the final analysis, these problems were mainly caused by my unreasonable design, and the registration function was registered Some of the secondary functions in are coupled to the registration methods, and these functions may be adjusted frequently, resulting in the instability of the registration interface.

In fact, the above code can do this:

Find 3 people: registrar, passer-by A, passer-by B.

Register: responsible for the user information into the database. After the database is successfully settled, call out: user XXX has successfully registered.

Passers by A and B, put up their ears. When they hear the voice of "XXX successfully registered", they immediately act to make the following response:

Passerby A: responsible for sending a registered email to XXX

Passerby B: responsible for sending coupon to XXX

Let's take a look:

The registrar is only responsible for putting the user information into the database and broadcasting a successful user registration message.

A and B are equivalent to a listener. They are only responsible for listening to the success message of user registration. When they hear that the message is generated, a and B will do their own things.

In this case, the Registrar can't perceive the existence of A/B. neither a nor B need to perceive the existence of the Registrar. A/B only pays attention to whether someone broadcasts the message that XXX registered successfully. When AB hears the message that someone broadcast the successful registration, they respond and rest at leisure.

This is a great way:

When you don't want to send coupons to users, you just need to remove B. at this time, you basically don't need to test. Just register B's code.

If the registration is successful, more businesses are needed. For example, you need to add points to users. You only need to add a listener C. After listening to the registration success message, you are responsible for adding points to users. At this time, you don't need to adjust the registration code at all. Developers and testers just need to ensure the correctness of listener C.

The above pattern is the event pattern.

Some concepts in event pattern
Event source: the trigger of an event. For example, the above registrar is the event source.

Event: the object that describes what happened, such as the above event: xxx successfully registered

Event listener: when listening to the event, do some processing, such as the above: passerby A, passerby B

Now we use the event mode to implement the business of user registration
Let's first define several classes related to events.

Event object

Represents the parent class of all events. There is a source field in it to represent the event source. Our custom event needs to inherit this class.

package com.javacode2018.lesson003.demo1.test0.event;
 
/**
 * Event object
 */
public abstract class AbstractEvent {
 
    //Event source
    protected Object source;
 
    public AbstractEvent(Object source) {
        this.source = source;
    }
 
    public Object getSource() {
        return source;
    }
 
    public void setSource(Object source) {
        this.source = source;
    }
}
Event listener

We use an interface to represent the event listener, which is a generic interface. The following type E represents the event type that the current listener needs to listen to. There is only one method in this interface to implement the event processing business. The defined listener needs to implement this interface.

package com.javacode2018.lesson003.demo1.test0.event;
 
/**
 * Event listener
 *
 * @param <E> Event types of interest to the current listener
 */
public interface EventListener<E extends AbstractEvent> {
    /**
     * This method handles events
     *
     * @param event Event object to respond to
     */
    void onEvent(E event);
}

Event broadcaster
Manage event listeners (register listeners & remove listeners, associate events with listeners)
Broadcast the event (broadcast the event to all listeners, and the listeners interested in the event will handle the event)

package com.javacode2018.lesson003.demo1.test0.event;
 
/**
 * Event broadcaster:
 * 1.Manage event listeners (register listeners & remove listeners, associate events with listeners)
 * 2.Broadcast the event (broadcast the event to all listeners, and the listeners interested in the event will handle the event)
 */
public interface EventMulticaster {
 
    /**
     * Broadcast the event to all listeners, and the listeners interested in the event will handle the event
     *
     * @param event
     */
    void multicastEvent(AbstractEvent event);
 
    /**
     * Add an event listener (the listener contains events that can be handled in the listener)
     *
     * @param listener Need to add listener
     */
    void addEventListener(EventListener<?> listener);
 
 
    /**
     * Remove event listener
     *
     * @param listener Listeners to be removed
     */
    void removeEventListener(EventListener<?> listener);
}
Event broadcast default implementation
package com.javacode2018.lesson003.demo1.test0.event;
 
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
/**
 * Simple implementation of event broadcaster
 */
public class SimpleEventMulticaster implements EventMulticaster {
 
    private Map<Class<?>, List<EventListener>> eventObjectEventListenerMap = new ConcurrentHashMap<>();
 
    @Override
    public void multicastEvent(AbstractEvent event) {
        List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(event.getClass());
        if (eventListeners != null) {
            for (EventListener eventListener : eventListeners) {
                eventListener.onEvent(event);
            }
        }
    }
 
    @Override
    public void addEventListener(EventListener<?> listener) {
        Class<?> eventType = this.getEventType(listener);
        List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(eventType);
        if (eventListeners == null) {
            eventListeners = new ArrayList<>();
            this.eventObjectEventListenerMap.put(eventType, eventListeners);
        }
        eventListeners.add(listener);
    }
 
    @Override
    public void removeEventListener(EventListener<?> listener) {
        Class<?> eventType = this.getEventType(listener);
        List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(eventType);
        if (eventListeners != null) {
            eventListeners.remove(listener);
        }
    }
 
    /**
     * Get the event types that the event listener needs to listen to
     *
     * @param listener
     * @return
     */
    protected Class<?> getEventType(EventListener listener) {
        ParameterizedType parameterizedType = (ParameterizedType) listener.getClass().getGenericInterfaces()[0];
        Type eventType = parameterizedType.getActualTypeArguments()[0];
        return (Class<?>) eventType;
    }
 
}

The above three classes support the whole time model. Next, we use the above three classes to achieve the registration function. The goal is: high cohesion and low coupling, making the registration logic easy to expand.

Custom user registration success event class
Inherited AbstractEvent class

package com.javacode2018.lesson003.demo1.test0.userregister;
 
import com.javacode2018.lesson003.demo1.test0.event.AbstractEvent;
 
/**
 * User registration success event
 */
public class UserRegisterSuccessEvent extends AbstractEvent {
    //user name
    private String userName;
 
    /**
     * Create user registration success event object
     *
     * @param source   Event source
     * @param userName Currently registered user name
     */
    public UserRegisterSuccessEvent(Object source, String userName) {
        super(source);
        this.userName = userName;
    }
 
    public String getUserName() {
        return userName;
    }
 
    public void setUserName(String userName) {
        this.userName = userName;
    }
}

User registration service

Responsible for implementing user registration logic

package com.javacode2018.lesson003.demo1.test0.userregister;
 
import com.javacode2018.lesson003.demo1.test0.event.EventMulticaster;
 
/**
 * User registration service
 */
public class UserRegisterService {
    //Event publisher
    private EventMulticaster eventMulticaster; //@0
 
    /**
     * Registered users
     *
     * @param userName user name
     */
    public void registerUser(String userName) { //@1
        //User registration (warehousing user information, etc.)
        System.out.println(String.format("Users[%s]login was successful", userName)); //@2
        //Broadcast events
        this.eventMulticaster.multicastEvent(new UserRegisterSuccessEvent(this, userName)); //@3
    }
 
    public EventMulticaster getEventMulticaster() {
        return eventMulticaster;
    }
 
    public void setEventMulticaster(EventMulticaster eventMulticaster) {
        this.eventMulticaster = eventMulticaster;
    }
}

@0: event publisher
@1: registerUser is responsible for user registration. There are two main internal tasks
@2: Simulate to drop user information into database
@3: Use event publisher eventPublisher to publish the message of successful user registration:

Now let's use spring to assemble the above objects

package com.javacode2018.lesson003.demo1.test0.userregister;
 
import com.javacode2018.lesson003.demo1.test0.event.EventListener;
import com.javacode2018.lesson003.demo1.test0.event.EventMulticaster;
import com.javacode2018.lesson003.demo1.test0.event.SimpleEventMulticaster;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
 
import java.util.List;
 
@Configuration
@ComponentScan
public class MainConfig0 {
 
    /**
     * Register a bean: event publisher
     *
     * @param eventListeners
     * @return
     */
    @Bean
    @Autowired(required = false)
    public EventMulticaster eventMulticaster(List<EventListener> eventListeners) { //@1
        EventMulticaster eventPublisher = new SimpleEventMulticaster();
        if (eventListeners != null) {
            eventListeners.forEach(eventPublisher::addEventListener);
        }
        return eventPublisher;
    }
 
    /**
     * Register a bean: user registration service
     *
     * @param eventMulticaster
     * @return
     */
    @Bean
    public UserRegisterService userRegisterService(EventMulticaster eventMulticaster) { //@2
        UserRegisterService userRegisterService = new UserRegisterService();
        userRegisterService.setEventMulticaster(eventMulticaster);
        return userRegisterService;
    }
}

There are two methods above, which are responsible for registering two bean s with the spring container.
@1: A bean is registered with the spring container: event publisher. The method passes in a List of EventListener type. This place will inject all the event listeners in the container and drop them into EventMulticaster.
@2: Registered a bean with the spring container: user registration service

Test case to simulate user registration

package com.javacode2018.lesson003.demo1;
 
import com.javacode2018.lesson003.demo1.test0.userregister.MainConfig0;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class EventTest {
 
    @Test
    public void test0() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig0.class);
        //Get user registration service
        com.javacode2018.lesson003.demo1.test0.userregister.UserRegisterService userRegisterService =
                context.getBean(com.javacode2018.lesson003.demo1.test0.userregister.UserRegisterService.class);
        //Simulate user registration
        userRegisterService.registerUser("Passerby a Java");
    }
 
}

Operation output

User [passerby a Java] successfully registered

Add the function of registering to send mail successfully
Next, add a function of successfully registering to send e-mail. You only need to customize a listener that listens to user registration success events. Other codes do not need to be changed, as follows

package com.javacode2018.lesson003.demo1.test0.userregister;
 
 
import com.javacode2018.lesson003.demo1.test0.event.EventListener;
import org.springframework.stereotype.Component;
 
/**
 * User registration success event listener - > responsible for sending email to user
 */
@Component
public class SendEmailOnUserRegisterSuccessListener implements EventListener<UserRegisterSuccessEvent> {
    @Override
    public void onEvent(UserRegisterSuccessEvent event) {
        System.out.println(
                String.format("For users[%s]Send registration success email!", event.getUserName()));
    }
}

The above class uses @ Component and will be automatically scanned and registered to the spring container.

Run the test case output again

User [passerby a Java] successfully registered
 Send registration success email to user [passerby a Java]!

Summary
Above, the main logic of registration (user information database) and the secondary business logic (sending mail) are decoupled through events. Secondary businesses are pluggable. For example, if you don't want to send mail, you only need to annotate @ Component on the mail listener, which is very convenient to expand.

The above classes related to events are all implemented by ourselves. In fact, these functions have been implemented in spring and are easier to use. Let's take you to experience them.

Implementing event pattern in Spring

Event related classes
Several classes related to events in spring need to be understood first. Here is a table to compare the classes related to events in spring with the classes defined above for your convenience.

These classes are similar to the code in our custom classes. If you are interested in them, you can check the source code, which will not be listed here.

Using spring events in a hard coded way 3 steps
Step 1: define the event
For custom events, you need to inherit the ApplicationEvent class,

Step 2: define the listener
To customize the event listener, you need to implement the ApplicationListener interface, which has a method onApplicationEvent that needs to be implemented to handle the events of interest.

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
 
    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);
 
}

Step 3: create an event broadcaster
Create the event broadcaster ApplicationEventMulticaster, which is an interface. You can implement this interface yourself or directly use the SimpleApplicationEventMulticaster provided by the system, as follows:

ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();

Step 4: register the event listener with the broadcaster
Register the event listener to the broadcaster ApplicationEventMulticaster, such as:

ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());

Step 5: publish events through the broadcaster
Broadcast event. Call the applicationeventmulticaster? Multicasevent method to broadcast the event. The listener interested in the event in the broadcaster will handle the event.

applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));

Now let's take a case to feel these five steps.

case
Implementation function: after the order in e-commerce is created successfully, an email is sent to the next person, and the function of sending email is implemented in the listener.

Code above

Event class: order creation success event

package com.javacode2018.lesson003.demo1.test1;
 
import org.springframework.context.ApplicationEvent;
 
/**
 * Order creation event
 */
public class OrderCreateEvent extends ApplicationEvent {
    //Order id
    private Long orderId;
 
    /**
     * @param source  Event source
     * @param orderId Order id
     */
    public OrderCreateEvent(Object source, Long orderId) {
        super(source);
        this.orderId = orderId;
    }
 
    public Long getOrderId() {
        return orderId;
    }
 
    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }
}

A listener: responsible for listening to order success events and sending emails

package com.javacode2018.lesson003.demo1.test1;
 
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
 
/**
 * Send email to user after order creation
 */
@Component
public class SendEmailOnOrderCreateListener implements ApplicationListener<OrderCreateEvent> {
    @Override
    public void onApplicationEvent(OrderCreateEvent event) {
        System.out.println(String.format("Order[%d]Create successfully, send email notification to the next person!", event.getOrderId()));
    }
}

test case

@Test
public void test2() throws InterruptedException {
    //Create event broadcaster
    ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
    //Register event listeners
    applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());
    //Broadcast event order creation event
    applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));
}

Operation output

The order [1] is created successfully. Send email notice to the next person!

Event support in ApplicationContext container

The above shows the use of events in spring, so we usually use it when we use spring?

No, no, it's just that I've shown you the principle.

In general, we will use the class ending with ApplicationContext as the spring container to start the application. The following two are more common

AnnotationConfigApplicationContext
ClassPathXmlApplicationContext

Let's look at a class diagram

Let's explain this picture:

1. Annotationconfiguapplicationcontext and ClassPathXmlApplicationContext inherit AbstractApplicationContext
 2.AbstractApplicationContext implements the ApplicationEventPublisher interface
 3.AbstractApplicationContext has a field of ApplicationEventMulticaster type inside

The third item above indicates that the event broadcaster applicationeventmulticasting has been integrated in the AbstractApplicationContext, which indicates that the specific event related functions are implemented in the AbstractApplicationContext through its internal applicationeventmulticasting, that is to say, the event functions are delegated to the internal applicationeventmulticasting for implementation Now.

ApplicationEventPublisher interface

There is a new interface ApplicationEventPublisher in the class diagram above. Let's see the source code

@FunctionalInterface
public interface ApplicationEventPublisher {
 
    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }
 
    void publishEvent(Object event);
 
}

This interface is used to publish events. Two methods defined internally are used to publish events.

Isn't there an ApplicationEventMulticaster interface in spring? Why is there another interface for publishing events here?

In the implementation class of this interface, for example, annotationconfigpplicationcontext internally delegates these two methods to applicationeventmulticaster ා multicasevent for processing.

Therefore, calling the publishEvent method in AbstractApplicationContext can also achieve the effect of broadcasting events. However, using AbstractApplicationContext can only broadcast events by calling the publishEvent method.

Get ApplicationEventPublisher object
If we want to get the ApplicationEventPublisher object in the ordinary bean, we need to implement the ApplicationEventPublisherAware interface

public interface ApplicationEventPublisherAware extends Aware {
    void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);
}

The spring container will automatically inject ApplicationEventPublisher through the setApplicationEventPublisher method above. Now we can use this to publish events.

To simplify the use of events, Spring provides two ways to use them

1. Face to face interface
2. Face @ EventListener annotation

Face to face interface
case
After the successful registration, the user can publish the event and then send the message in the listener.

User registration event

Need to inherit ApplicationEvent

package com.javacode2018.lesson003.demo1.test2;
 
import org.springframework.context.ApplicationEvent;
 
/**
 * User registration event
 */
public class UserRegisterEvent extends ApplicationEvent {
    //user name
    private String userName;
 
    public UserRegisterEvent(Object source, String userName) {
        super(source);
        this.userName = userName;
    }
 
    public String getUserName() {
        return userName;
    }
}

Send mail listener

Need to implement the ApplicationListener interface

package com.javacode2018.lesson003.demo1.test2;
 
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
 
/**
 * User registered to send mail successfully
 */
@Component
public class SendEmailListener implements ApplicationListener<UserRegisterEvent> {
 
    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
        System.out.println(String.format("For users[%s]Send registration success email!", event.getUserName()));
 
    }
}

User registration service

It provides the function of user registration internally and publishes user registration events

package com.javacode2018.lesson003.demo1.test2;
 
 
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
 
/**
 * User registration service
 */
@Component
public class UserRegisterService implements ApplicationEventPublisherAware {
 
    private ApplicationEventPublisher applicationEventPublisher;
 
    /**
     * Responsible for user registration and event release functions
     *
     * @param userName user name
     */
    public void registerUser(String userName) {
        //User registration (warehousing user information, etc.)
        System.out.println(String.format("Users[%s]login was successful", userName));
        //Publish registration success event
        this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, userName));
    }
 
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { //@1
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

Note that the above implements the ApplicationEventPublisherAware interface. The spring container will inject ApplicationEventPublisher through @ 1, and then we can use this to publish events.

A spring configuration class

package com.javacode2018.lesson003.demo1.test2;
 
import org.springframework.context.annotation.ComponentScan;
 
@ComponentScan
public class MainConfig2 {
}
//Upper test case
@Test
public void test2() throws InterruptedException {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig2.class);
    context.refresh();
    //Get user registration service
    com.javacode2018.lesson003.demo1.test2.UserRegisterService userRegisterService =
            context.getBean(com.javacode2018.lesson003.demo1.test2.UserRegisterService.class);
    //Simulate user registration
    userRegisterService.registerUser("Passerby a Java");
}

Operation output

User [passerby a Java] successfully registered
 Send registration success email to user [passerby a Java]!

principle
In the process of creating a bean, the spring container will determine whether the bean is of ApplicationListener type, and then register it as a listener in abstractapplicationcontext ා applicationeventmulticaster. This source code is in the following method. You can have a look at it if you are interested

org.springframework.context.support.ApplicationListenerDetector#postProcessAfterInitialization

Summary

From the above case, we can see that event class and listener class are based on some interfaces related to events in spring to realize event functions. This way is called face-to-face interface.

Annotation mode for @ EventListener

usage
The above is to create a listener through the interface. spring also provides a listener through the @ EventListener annotation, which is directly annotated on a bean method. Then this method can be used to handle the events of interest. It is simpler to use, as follows: the method parameter type is the event type:

@Component
public class UserRegisterListener {
    @EventListener
    public void sendMail(UserRegisterEvent event) {
        System.out.println(String.format("For users[%s]Send registration success email!", event.getUserName()));
    }
}

case

After the registration is successful: two listeners are available: one is responsible for sending emails and the other is responsible for sending coupons. No other code can be found. As in the above case, it mainly depends on the code of the listener, as follows:

package com.javacode2018.lesson003.demo1.test3;
 
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
 
/**
 * User registration listener
 */
@Component
public class UserRegisterListener {
    @EventListener
    public void sendMail(UserRegisterEvent event) {
        System.out.println(String.format("For users[%s]Send registration success email!", event.getUserName()));
    }
 
    @EventListener
    public void sendCompon(UserRegisterEvent event) {
        System.out.println(String.format("For users[%s]Send coupons!", event.getUserName()));
    }
}

This case code

com.javacode2018.lesson003.demo1.EventTest#test3

Operation results

User [passerby a Java] successfully registered
 Send coupons to user [passerby a Java]!
Send registration success email to user [passerby a Java]!

principle
The source code for processing @ EventListener annotation in spring is located in the following method

org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated

EventListenerMethodProcessor implements the smartinitializingsingsingleton interface. The aftersingletons instantiated method in the smartinitializingssingleton interface will be called by the spring container after all the singleton beans are created. This content can be seen as follows: detailed explanation of bean life cycle

idea supports annotation better

The way of annotation is to implement the listener. idea supports this better. A headset will be displayed in the place where the time is released. When you click this headset, spring will help us list which listeners this event has

Click the headset to list 2 monitors, which can be quickly located to the monitor, as follows

There is also a broadcast icon in the listener, as shown below

Click the broadcast icon above, you can quickly navigate to the place where the event is released, which is quite convenient.

Listener supports sorting function

If an event has more than one listener, by default, the listener execution order is unordered, but we can specify the order for the listener.

Implementation of listener through interface
If the custom listener is implemented through the ApplicationListener interface, there are three ways to specify the listener order

Mode 1: Implementation org.springframework.core.Ordered interface
You need to implement a getOrder method to return the order value. The smaller the value, the higher the order

int getOrder();

Mode 2: Implementation org.springframework.core.PriorityOrdered interface
The PriorityOrdered interface inherits the Ordered interface in method 1, so if you implement the PriorityOrdered interface, you also need to implement the getOrder method.

Mode 3: use on class@ org.springframework.core.annotation.Order annotation
Take a look at the source code of this annotation

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
 
    int value() default Ordered.LOWEST_PRECEDENCE;
 
}

The value attribute is used to specify the order

Sorting rules in these ways

PriorityOrdered#getOrder ASC,Ordered or@Order ASC 

Implementation of event listener through @ EventListener
You can use the @ order annotation to annotate the order on the method that annotates @ EventListener, such as:

@EventListener
@Order(1)
public void sendMail(com.javacode2018.lesson003.demo1.test3.UserRegisterEvent event) {
    System.out.println(String.format("For users[%s]Send registration success email!", event.getUserName()));
}

case

package com.javacode2018.lesson003.demo1.test4;
 
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
 
/**
 * User registration listener
 */
@Component
public class UserRegisterListener {
    @EventListener
    @Order(1)
    public void sendMail(UserRegisterEvent event) {
        System.out.println(String.format("[%s],For users[%s]Send registration success email!", Thread.currentThread(), event.getUserName()));
    }
 
    @EventListener
    @Order(0)
    public void sendCompon(UserRegisterEvent event) {
        System.out.println(String.format("[%s],For users[%s]Send coupons!", Thread.currentThread(), event.getUserName()));
    }
}

The coupon will be sent first, then the email.

In the above output, thread information is also output by the way.

Corresponding test case

com.javacode2018.lesson003.demo1.EventTest#test4

Operation output

[Thread[main,5,main]], user [passerby a Java] successfully registered
 [Thread[main,5,main]], send coupons to user [passerby a Java]!
[Thread[main,5,main]], send registration success email to user [passerby a Java]!

It can be seen from the output that the execution of the above programs are executed in the main thread, which means that the logic in the listener and the registration logic are executed in the same thread. At this time, if the logic in the listener is time-consuming or fails, it will directly lead to the registration failure. Generally, we can put some non main logic into the listener for execution, as for the success or failure of these non main logic Fail, it's better not to affect the main logic, so we'd better separate the operation of the listener from the main business and execute it in different threads. The main business doesn't need to pay attention to the results of the listener. This function is supported in spring. Let's continue.

Listener asynchronous mode

Let 's see how to achieve it?

The listener is finally called through the internal implementation of applicationeventmulticasting, so we focus on this class. This class has an implementation class simpleapplicationeventmulticasting by default, which supports the asynchronous call of the listener. There is a field in it:

private Executor taskExecutor;

Friends familiar with high concurrency are familiar with the Executor interface, which can be used to perform some tasks asynchronously.

Our common thread pool class java.util.concurrent.ThreadPoolExecutor implements the Executor interface.

Let's take a look at the call of event listener in SimpleApplicationEventMulticaster, which will finally execute the following method

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) { //@1
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

The invokeListener method above calls the listener internally. From the code @ 1, we can see that if the current executor is not empty, the listener will be called asynchronously. So if you need to be asynchronous, you only need to make the executor not empty, but by default, the executor is empty. At this time, we need to set a value for it. Next, we need to see how to create it in the container Announcer, we'll intervene in that place.

Generally, the container we use is of the type AbstractApplicationContext. We need to see how the broadcaster in AbstractApplicationContext is initialized. This is the method below. When the container is started, it will be called to initialize the event broadcaster in AbstractApplicationContext applicationEventMulticaster

public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
 
protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        this.applicationEventMulticaster =
            beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
    }
    else {
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
    }
}

The above logic explains: judge whether there is a bean named applicationeventmulticasting in the spring container. If there is one, use it as the event broadcaster. Otherwise, create a simpleapplicationeventmulticasting as the broadcaster and register it in the spring container.

We can draw a conclusion from the above: we only need to customize a bean with the type of simpleapplicationeventmulticasting and the name of applicationeventmulticasting. By the way, we can set a value for the executor to implement the asynchronous execution of the listener.

The specific implementation is as follows

package com.javacode2018.lesson003.demo1.test5;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
 
import java.util.concurrent.Executor;
 
@ComponentScan
@Configuration
public class MainConfig5 {
    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() { //@1
        //Create an event broadcaster
        SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();
        //Provide a thread pool for the broadcaster to call the event listener
        Executor executor = this.applicationEventMulticasterThreadPool().getObject();
        //Set asynchronous actuator
        result.setTaskExecutor(executor);//@1
        return result;
    }
 
    @Bean
    public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool() {
        ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
        result.setThreadNamePrefix("applicationEventMulticasterThreadPool-");
        result.setCorePoolSize(5);
        return result;
    }
}

@1: An event broadcaster named applicationEventMulticaster is defined, and a thread pool is set internally to call the listener asynchronously

Test case corresponding to this code

com.javacode2018.lesson003.demo1.EventTest#test5

Operation output

The current thread [Thread[main,5,main], user [passerby a Java] registered successfully
 The current Thread[applicationEventMulticasterThreadPool-2,5,main]] sends the registration success email to the user [passerby a Java]!
The current Thread[applicationEventMulticasterThreadPool-1,5,main]] will issue some coupons to the user [passerby Java]!

At this time, the effect of listener asynchronous execution is realized.

Suggestions on event use
Do events in spring use interfaces or annotations? It's OK to use any specific method, but it's better to use one method uniformly within the company

The mode of asynchronous event usually places some non main businesses in the listener for execution, because there is a risk of failure in the listener, so attention should be paid when using it. If it is only for decoupling, but the secondary business to be decoupled must be successful, message middleware can be used to solve these problems.

This is the end of the event. If you have any questions, please leave a message for discussion.

Case source code

https://gitee.com/javacode2018/spring-series

All the case codes of passerby a java will be put on this in the future. watch it and you can continue to pay attention to the dynamics.

Spring collection
Part 1 of the spring series: why learn spring?

More good articles
Java high concurrency series (34 in total)

Posted by fordyh on Fri, 12 Jun 2020 04:11:34 -0700