General documentation: Article directory Github: github.com/black-ant

A. The preface

This document improves the concept of a Listener in the Spring system:

Main contents:

  • Process the Listener
  • Synchronous and asynchronous processing of the Listener
  • Common Listener processing

2. Basic use of the Listener

Basic use includes four steps:

  • Build a TranTO to host the data
  • Build an Event for publishing
  • Build a Listener to accept events and process them
  • Publish events in the main business

2.1 Data carrier

The data carrier is used to carry data to the Listener during the publication event

public class ListenerTranTO {

    private String eventName;

    private Map<String, String> eventInfo;

    public String getEventName(a) {
        return eventName;
    }

    public void setEventName(String eventName) {
        this.eventName = eventName;
    }

    public Map<String, String> getEventInfo(a) {
        return eventInfo;
    }

    public void setEventInfo(Map<String, String> eventInfo) {
        this.eventInfo = eventInfo;
    }


    @Override
    public String toString(a) {
        return "ListenerTranTO{" +
                "eventName='" + eventName + '\' ' +
                ", eventInfo=" + eventInfo +
                '} '; }}Copy the code

2.2 Build publish events

As you can see, Event builds an object based on ApplicationEvent

  public class DefaultEvent extends ApplicationEvent {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public DefaultEvent(ListenerTranTO tranTO) {
        super(tranTO); }}Copy the code

2.3 Build a Listener to receive and process events

@Component
public class DefaultListener implements ApplicationListener<DefaultEvent> {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void onApplicationEvent(DefaultEvent event) {
        logger.info("------> DefaultEvent Listner , Properties [{}] <-------", String.valueOf(event.getSource())); }}Copy the code

2.4 Publishing events in Active Services

 @Autowired
    private ApplicationContext context;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        logger.info("------> Default Event Publish Start >>>>> <-------");

        ListenerTranTO tranTO = new ListenerTranTO();

        Map<String, String> infoMap = new HashMap<>();
        infoMap.put("info"."This is in Info");
        infoMap.put("message"."Listener Success");

        tranTO.setEventInfo(infoMap);
        tranTO.setEventName("DefaultListener");

        context.publishEvent(new DefaultEvent(tranTO));

        logger.info("------> Default Event Publish End >>>>> [{}] <-------", tranTO.toString()); }}Copy the code

3. The Listener process is resolved

This section describes the application of the Listener in the system, first look at the common Listener, first look at the official definition of ApplicationListener: An interface implemented by an application EventListener, based on the standard java.util.EventListener interface of the observer design pattern.

Here’s a review of the concepts associated with the observer pattern:

Observer pattern intent: Defines a one-to-many dependency between objects so that when an object’s state changes, all dependent objects are notified and automatically updated.

Reference @ blog.csdn.net/zzg19950824…

public interface Observer {  
    public void update(a);  
}  
// Two implementation classes:

public class Observer1 implements Observer {  
  
    @Override  
    public void update(a) {  
        System.out.println("observer1 has received!"); }}public class Observer2 implements Observer {  
  
    @Override  
    public void update(a) {  
        System.out.println("observer2 has received!"); }}// The Subject interface and implementation class:

public interface Subject {  
      
    /* Add observer */  
    public void add(Observer observer);  
      
    /* Delete observer */  
    public void del(Observer observer);  
      
    /* Notify all observers */  
    public void notifyObservers(a);  
      
    /* Self operation */  
    public void operation(a);  
}  

public abstract class AbstractSubject implements Subject {  
  
    private Vector<Observer> vector = new Vector<Observer>();  
    @Override  
    public void add(Observer observer) {  
        vector.add(observer);  
    }  
  
    @Override  
    public void del(Observer observer) {  
        vector.remove(observer);  
    }  
  
    @Override  
    public void notifyObservers(a) {  
        Enumeration<Observer> enumo = vector.elements();  
        while(enumo.hasMoreElements()){ enumo.nextElement().update(); }}}public class MySubject extends AbstractSubject {  
  
    @Override  
    public void operation(a) {  
        System.out.println("update self!"); notifyObservers(); }}// Test class:

public class ObserverTest {  
  
    public static void main(String[] args) {  
        Subject sub = new MySubject();  
        sub.add(new Observer1());  
        sub.add(newObserver2()); sub.operation(); }}/ / output:
update self!
observer1 has received!
observer2 has received!
Copy the code

3.1 Triggering mode of the Listener

Listerner triggers are mainly as follows:

  • Triggered automatically when the container starts
  • Publish publishEvent manually

Type 1: The container automatically triggers the process

C - AbstractApplicationContext # refresh (), most of them are in this process to complete the trigger, usually issued by the publishthis.applicationContext.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));    
C50- AbstractApplicationContext
    M50_10- publishEvent(ApplicationEvent event)
    M50_11- publishEvent(Object event, @Nullable ResolvableType eventType)
    
// Step 1: Publish the Event
public void publishEvent(ApplicationEvent event) {
	publishEvent(event, null);
}


protected void publishEvent(Object event, @Nullable ResolvableType eventType) {

    // Decorate event as an ApplicationEvent if necessary
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
        applicationEvent = (ApplicationEvent) event;
    } else {
        // If not ApplicationEvent, build PayloadApplicationEvent -> PS:M50_11_01
        applicationEvent = new PayloadApplicationEvent<>(this, event);
        if (eventType == null) {
            // Get the Object type inside the event
            eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
        }
    }

    // If possible, start multicast immediately, or delay multicast after the multicaster is initialized -> PS:M50_11_02
    if (this.earlyApplicationEvents ! =null) {
        this.earlyApplicationEvents.add(applicationEvent);
    } else {
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }

    Events can also be published through the parent context
    if (this.parent ! =null) {
        if (this.parent instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
        }else {
            this.parent.publishEvent(event); }}}// PS:M50_11_01 What is PayloadApplicationEvent?C- PayloadApplicationEvent ? - ApplicationEvent i-ResolvAbletypeProvider with any payload F-private finalT payload; M-getresolvabletype () : Returns the ResolvableType - that describes this instancereturn ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getPayload()));
    
Copy the code

PS: What is the multicast of the M50_11_02 Listener?

What is the Listener multicast?

Multicast all events to all registered listeners, letting them ignore events they are not interested in. Listeners typically perform the appropriate Instanceof check on the incoming event object.

At the same time, it is possible to process asynchronously, specifying an alternative task executor that causes the listener to execute in a different thread, such as in a thread pool

Bottom line: Think of it as the primary publisher of the Listener


// Step 1: Event multicast publishing mode
if (this.earlyApplicationEvents ! =null) {
    this.earlyApplicationEvents.add(applicationEvent);
} else {
    getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}


// Step 2: Where the object is createdC - AbstractApplicationContext F - Set < ApplicationEvent > earlyApplicationEvents: ApplicationEvents released before multicast device Settings/ / events of multicast, create a place in AbstractApplicationContext:
C- AbstractApplicationContext
    M- refush
        - initApplicationEventMulticaster()

protected void initApplicationEventMulticaster(a) {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    / / if APPLICATION_EVENT_MULTICASTER_BEAN_NAME (applicationEventMulticaster), directly from the Bean factory
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
    } else {
        / / to build a new SimpleApplicationEventMulticaster and registration
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); }}// Step 3: Look at earlyApplicationEvents
C- AbstractApplicationContext
protected void registerListeners(a) {
        // First register statically specified listeners
        for(ApplicationListener<? > listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); }// Obtain all applicationListeners. Here, all applicationListeners are obtained by type
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true.false);
        for (String listenerBeanName : listenerBeanNames) {
            / / add the Listener to ApplicationEventMulticaster
            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }

        // Publish early application events
        // Get the original earlyEventsToProcess object for cache and clear the tin
        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
        this.earlyApplicationEvents = null;
        if(! CollectionUtils.isEmpty(earlyEventsToProcess)) {for(ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster().multicastEvent(earlyEvent); }}}// As you can see, multicastEvent is still called to publish the message


Copy the code

3.2 Cyclic processing of the Listener

Calling multicastEvent for the final operation involves several steps:

  • ResolveDefaultEventType Gets the type of the event
  • GetTaskExecutor gets Executor -> PS:M50_12_1
  • Get all listenRs
// Step 2: Loop through all the listenersC50 - AbstractApplicationContext M50_12 - multicastEvent (ApplicationEvent event) - getApplicationListeners access to all the Listeners  -> M50_12_01/** * Loop Listener **/
C- SimpleApplicationEventMulticaster
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType ! =null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    
    // Get the Listener object
    for(ApplicationListener<? > listener : getApplicationListeners(event, type)) {if(executor ! =null) {
            executor.execute(() -> invokeListener(listener, event));
        }else{ invokeListener(listener, event); }}}/ / PS: ApplicationEventMulticaster interface, the main implementation class is SimpleApplicationEventMulticaster
C- ApplicationEventMulticaster
    M- void addApplicationListener(ApplicationListener
        listener): Adds a listener to notify everything M-void addApplicationListenerBean(String listenerBeanName): Adds a listener bean to notify all events M-void removeApplicationListener(ApplicationListener
        listener): Removes listener M- from the notification listvoid removeApplicationListenerBean(String listenerBeanName): Removes the listener bean M- from the notification listvoid removeAllListeners(a): Deletes all listeners M- registered with this multicast broadcastvoid multicastEvent(ApplicationEvent event): multicast the given application event to the appropriate listener M-void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType): Multicast the given application events to the appropriate listeners/ / PS: supplement SimpleApplicationEventMulticaster see below


Copy the code

The following is the ApplicationListener class obtained through eventType

C- AbstractApplicationEventMulticaster
// M50_12_01 getApplicationListeners Main process
protectedCollection<ApplicationListener<? >> getApplicationListeners( ApplicationEvent event, ResolvableType eventType) { Object source = event.getSource(); Class<? > sourceType = (source ! =null ? source.getClass() : null);
    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

    // Quick check for existing entries on ConcurrentHashMap...
    ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
    if(retriever ! =null) {
            return retriever.getApplicationListeners();
    }

    if (this.beanClassLoader == null ||(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                        (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
        // Listener Fully synchronized build and cache of the retriver
        synchronized (this.retrievalMutex) {
            retriever = this.retrieverCache.get(cacheKey);
            if(retriever ! =null) {
                return retriever.getApplicationListeners();
            }
            retriever = new ListenerRetriever(true);
            // Get the ApplicationListener collectionCollection<ApplicationListener<? >> listeners =retrieveApplicationListeners(eventType, sourceType, retriever);this.retrieverCache.put(cacheKey, retriever);
            returnlisteners; }}else {
        // There is no need to synchronize without listening to retrieve the cache
        return retrieveApplicationListeners(eventType, sourceType, null);
    }
}


C- AbstractApplicationContext
// Actually retrieve the application listener for the given event and source type
privateCollection<ApplicationListener<? >> retrieveApplicationListeners( ResolvableType eventType,@NullableClass<? > sourceType,@NullableListenerRetriever retriever) { List<ApplicationListener<? >> allListeners =newArrayList<>(); Set<ApplicationListener<? >> listeners; Set<String> listenerBeans;// This is usually a ListenerRetriever, which is a Helper class that encapsulates a specific set of target listeners, allowing efficient retrieval of pre-filtered listeners
        Each event type and source type will cache an instance of this Helper class
        synchronized (this.retrievalMutex) {
            // Get the Listener object
            listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
            listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
        }

        / / added programmatically registered listener, including listener from ApplicationListenerDetector (singleton beans and bean) ans).
        for(ApplicationListener<? > listener : listeners) {// Determine whether the Event is supported
            if (supportsEvent(listener, eventType, sourceType)) {
                if(retriever ! =null) { retriever.applicationListeners.add(listener); } allListeners.add(listener); }}// Add a listener by bean name, possibly overlapping with the one registered programmatically above
        if(! listenerBeans.isEmpty()) {// Since this is the Bean name, you need to build from the BeanFactory
            ConfigurableBeanFactory beanFactory = getBeanFactory();
            
            for (String listenerBeanName : listenerBeans) {
                try {
                      // Verify whether the current Event is supported
                    if(supportsEvent(beanFactory, listenerBeanName, eventType)) { ApplicationListener<? > listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);if(! allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {if(retriever ! =null) {
                                if (beanFactory.isSingleton(listenerBeanName)) {
                                    retriever.applicationListeners.add(listener);
                                }
                                else{ retriever.applicationListenerBeans.add(listenerBeanName); } } allListeners.add(listener); }}else {
                        Object listener = beanFactory.getSingleton(listenerBeanName);
                        if(retriever ! =null) { retriever.applicationListeners.remove(listener); } allListeners.remove(listener); }}catch (NoSuchBeanDefinitionException ex) {
                }
            }
        }
        
        // select allListeners
        AnnotationAwareOrderComparator.sort(allListeners);
        if(retriever ! =null && retriever.applicationListenerBeans.isEmpty()) {
            retriever.applicationListeners.clear();
            retriever.applicationListeners.addAll(allListeners);
        }
        return allListeners;
    }
    

/ / get the Listener
C- AbstractApplicationEventMulticaster
    // Helper class, which encapsulates a specific set of target listeners. allows efficient retrieval of pre-filtered listeners. //PVC- ListenerRetriever F- Set<ApplicationListener<? >> applicationListeners =new LinkedHashSet<>()
        F- Set<String> applicationListenerBeans = new LinkedHashSet<>()
    

Copy the code

PS:M50_12_1: getTaskExecutor Obtains an Executor

Returns the current task executor for this multicast

3.3 Invoke agent

This is where the actual call is made

C- SimpleApplicationEventMulticaster M- invokeListener(ApplicationListener<? > listener, ApplicationEvent event) - Get ErrorHandler - doInvokeListener agent listener - If an exception occurs, errorHandler.handleError(err) M- doInvokeListener(ApplicationListener listener, ApplicationEvent event) - the listener. OnApplicationEvent (event) launch event processingStep 3: Invoke Listener class
protected void invokeListener(ApplicationListener
        listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if(errorHandler ! =null) {
        try {
            doInvokeListener(listener, event);
        }catch(Throwable err) { errorHandler.handleError(err); }}else{ doInvokeListener(listener, event); }}/ / the Event processing
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
	listener.onApplicationEvent(event);
} 
    

Copy the code

Supplementary timing for storing the Listener


// There are four places to add Listener objects to the SetC- AbstractApplicationEventMulticaster # addApplicationListener(ApplicationListener<? > listener) ? To manually add the Listener, specific what would't call the C - # AbstractApplicationEventMulticaster retrieveApplicationListeners? - Here it is added from defaultRetriever to the passed ListenerRetrieverCopy the code

Iv. Asynchronous processing by SpringListener

The Listener is non-asynchronous when asynchrony is not enabled

@Configuration
@EnableAsync
public class AsyncListenerConfiguration implements AsyncConfigurer {

    /** * Spring Async configuration **@return* /
    @Override
    public Executor getAsyncExecutor(a) {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        // Set the number of thread pools
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadPoolTaskExecutor.setMaxPoolSize(20);
        threadPoolTaskExecutor.setQueueCapacity(50);
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(a) {
        return null; }}/** * Async listener **/
@Component
public class AsyncListener {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /** * Use Async for asynchronous processing **@param defaultEvent
     */
    @Async
    @EventListener
    public void doAsyncEvent(DefaultEvent defaultEvent) {
        logger.info("------> async :[{}] Thread :[{}]<-------", defaultEvent.getSource(), Thread.currentThread().getId()); }}// Publish the event
context.publishEvent(new DefaultEvent(tranTO));
try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}



2021-05-31 15:02:53.284  INFO 23640 --- [           main] c.g.s.s.demo.listener.DefaultListener    : ------> DefaultEvent Listner , Properties [ListenerTranTO{eventName='DefaultListener', eventInfo={message=Listener Success, info=This is in Info}}] <-------
2021-05-31 15:02:53.287  INFO 23640- [the main] C.G.S.S.D emo. Listener. DefaultListener: -- -- -- -- -- - > the listener Thread: [1] < -- -- -- -- -- -- --2021-05-31 15:02:53.297  INFO 23640 --- [lTaskExecutor-1] C.G.S.S.D emo. Listener. AsyncListener: -- -- -- -- -- - > through asynchronous listening: [ListenerTranTO {eventName ='DefaultListener', eventInfo={message=Listener Success, info=This is in Info}}] , Thread is :[330] < -- -- -- -- -- -- --2021-05-31 15:03:03.288  INFO 23640 --- [           main] c.g.s.source.demo.listener.TestListener  : ------> Default Event Publish End >>>>> [ListenerTranTO{eventName='DefaultListener', eventInfo={message=Listener Success, info=This is in Info}}]  -- [1] < -- -- -- -- -- -- --// As you can see, the synchronous listening stops for 10 seconds after publication, while the asynchronous listening processes successfully
    
/ / principle:Asynchronous proxy AsyncExecutionInterceptor: mainly methodCopy the code

conclusion

This article improves the Listener module in Spring system, which can be divided into two situations:

Synchronous: AbstractApplicationContext cycle all the Listener for processing Asynchronous: first of all to agent method, when invoked, by Async allocation thread pool open a new thread for processing

The appendix

# How to register a Listener

Reprinted from @ www.cnblogs.com/linlf03/p/1…

Method 1: Configure listeners in spring.factories

/

public class FirstListener  implements ApplicationListener<ApplicationStartedEvent> {
 
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("hello, first listener"); }}// Add a configuration listener to the spring.factories file
/ / principle can look at this article: https://juejin.cn/post/6955489109225930789

org.springframework.context.ApplicationListener=com.example.demo.listener.FirstListener

Copy the code

Method 2: Add the Listener to the Main function

public static void main(String[] args) {
         
    SpringApplication springApplication = new SpringApplication(Sb2Application.class);
    springApplication.addListeners(new SecondListener());
    springApplication.run(args);
}

Copy the code

Method 3: Configure the parameters in the application.propeties file

context.listener.classes=.... listener.classCopy the code

Method 4: Add a listener to the related module

In some scenarios, you can add an AddListener to the Web container

  • JPA @ EntityListeners

Method five: annotate

@EventListener
public void event(Object event){
    System.out.println("MyEventHandle receives event:" + event.getClass());
}

Copy the code

Method five: WebListener

@WebListener
public class DefaultHttpSessionListener implements HttpSessionListener {
    / /...
}

// PS: Application method @servletComponentScan


Copy the code

# WebListener (same as ServletContextListener)

WebListener is also one of the subinterfaces of EventListener. This Listener is used to implement the operation of the Session

@WebListener
public class DefaultHttpSessionListener implements HttpSessionListener {

    private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpSessionListener.class);

    private final AtomicInteger counter = new AtomicInteger();

    @Override
    public void sessionCreated(HttpSessionEvent se) {

        LOG.info("New session is created. Adding Session to the counter.");
        counter.incrementAndGet();  //incrementing the counter
        updateSessionCounter(se);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        LOG.info("Session destroyed. Removing the Session from the counter.");
        counter.decrementAndGet();  //decrementing counter
        updateSessionCounter(se);
    }

    private void updateSessionCounter(HttpSessionEvent httpSessionEvent) {
        //Let's set in the context
        httpSessionEvent.getSession().getServletContext()
                .setAttribute("activeSession", counter.get());
        LOG.info("Total active session are {} ", counter.get()); }}// PS: Application method @servletComponentScanImplementation process of the scheme:public void tellNew(a) {

        // Notify interested session event listeners
        fireSessionEvent(Session.SESSION_CREATED_EVENT, null);

        // Notify interested application event listeners
        Context context = manager.getContext();
        Object listeners[] = context.getApplicationLifecycleListeners();
        if(listeners ! =null && listeners.length > 0) {
            HttpSessionEvent event =
                new HttpSessionEvent(getSession());
            for (int i = 0; i < listeners.length; i++) {
                if(! (listeners[i]instanceof HttpSessionListener))
                    continue;
                HttpSessionListener listener =
                    (HttpSessionListener) listeners[i];
                try {
                    context.fireContainerEvent("beforeSessionCreated",
                            listener);
                    listener.sessionCreated(event);
                    context.fireContainerEvent("afterSessionCreated", listener);
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    try {
                        context.fireContainerEvent("afterSessionCreated",
                                listener);
                    } catch (Exception e) {
                        // Ignore
                    }
                    manager.getContext().getLogger().error
                        (sm.getString("standardSession.sessionEvent"), t); }}}}// Processing logic of ContextServletListenerRegistrationBean in to registerCopy the code