prologue

Today a guy asked me out of the blue. He was asked in an interview how does Nacos integrate with Spring? The interviewer then asked him what is the typical application of the Observer pattern in Spring ??????

He couldn’t go on and failed the interview; I was really speechless, on the spot snarled: “pig intestines! Nacos is integrated using the event mechanism in Spring, which is typical of the Observer model.” He looked at me with a vacant face…………. ;

A, start

Before you go blind, follow the usual steps and start with an example program;

1.1) Create a custom business Bean

public class ListenerTestBean1 {

    /** * Attribute description: Identifies **@date: 2021/4/50005 8:22 PM */
    private Integer key;

    /** * Attribute description: Content **@date: 2021/4/50005 8:22 PM */
    private String value;

    /** * listener test class -- 1 **@author : XXSD
     * @date: 2021/4/50005 8:24 PM */
    public ListenerTestBean1(a) {}... The get and set methods....... are omitted }Copy the code

1.2) Create test event objects

public class TextEvent extends ApplicationEvent {

    private static final long serialVersionUID = -572546991703136392L;
    /** * Attribute description: Event identifier *@date: 2021/4/50005 8:27 PM */
    private final String eventKey;

    /**
     * Create a new ApplicationEvent.
     *
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public TextEvent(Object source, String eventKey) {
        super(source);
        this.eventKey = eventKey;
    }

    /** * Function Description: Event id *@author : XXSD
    * @date: 2021/4/50005 8:28 PM */
    public String getEventKey(a) {
        returneventKey; }}Copy the code

1.3) Create a listener object

@Component
public class ListenerTextEvent implements ApplicationListener<TextEvent> {
    /**
     * Handle an application event.
     *
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(TextEvent event) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.err.println("Monitor event:" + event.getSource());
        System.err.println("Obtained event id:"+ event.getEventKey()); }}Copy the code

1.4) Create configuration classes and launch objects

@ComponentScan(basePackages = {"xuexi.listener"})
public class ListenerConfig {}Copy the code
public static void main(String[] values){
    final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(ListenerConfig.class);

    final ListenerTestBean1 listenerTestBean1 = new ListenerTestBean1(1);
    System.err.println("Listener test class -- 1 initialized, start pushing events");
    annotationConfigApplicationContext.publishEvent(new TextEvent(listenerTestBean1, "Test - 1"));
    System.err.println("Executed");
}
Copy the code

Okay, so this is basically a simple Demo, so let’s start it up and see what it looks like;

Ii. Event basis

2.1) Preconditions

To be a complete event, there are three necessary conditions:

  • Event object: ApplicationEvent, used to describe the information about the event;
  • Event publishing: ApplicationEventMulticaster to release, push the event, is the embodiment of the design patterns – the observer pattern, the corresponding relationship between maintenance time and event listener, in the event responsible for notifying listeners related process;
  • Event listener: ApplicationListener, used to receive and respond to the event information processing;

My girlfriend has just left the house. I hear a dark cloud gathering and there is a thunder. At this moment, I take the umbrella and rush downstairs. The whole process is actually a process of releasing, pushing and processing an event; The clouds gathered and there was a thunderclap — an event; The enemy will arrive at the battlefield in 3 seconds……… Hehe hehe hehe);

2.2) Events provided in Spring

In the example, we use our own custom events; There are some events built into Spring;

These events are important events built into Spring;

  • ContextRefreshedEvent

Refreshed when the container is instantiated or refreshed, that is, after the entire container is refreshed. (Remember the Spring core steps, and this event is triggered in the final step, finishRefresh(), which has the fields: ContextRefreshedEvent); For example, when the refresh method of the core processing method is called; Note that “container instantiated” means that all beans are loaded, the post-processor is activated, all singleton beans are instantiated, and all objects in the container are ready to use.

PS: Note that refresh can be triggered multiple times if the container supports hot overloading. Note that XmlWebApplicationContext supports hot flushing; GenericApplicationContext does not support hot flush; Here’s an interview question based on the interviewer’s mindset: “How do I do something after all the beans have been created?” It’s actually event handling here; A custom listener can be created to listen for this event: at this point, I might ask: what else can I do? Isn’t it evil?? Everyone knows the rear processor is a tiling of Spring, so here to tell you another expansion point, by the way: SmartInitializingSingleton this interface is also a tiling, in later chapters will be mentioned;

/** * Class description: ContextRefreshedEvent event listener * <br /> * This event is triggered at the last step of the Spring core method execution; * source location: org. Springframework. Context. Support. AbstractApplicationContext# finishRefresh () * source: publishEvent(new ContextRefreshedEvent(this)); * *@author XXSD
 * @version 1.0.0
 * @date2021/4/60006 10:18 am */
@Component
public class ContextRefreshedEventListener {

    /** ** ContextRefreshedEvent event processing **@author : XXSD
     * @date: 2021/4/60006 10:21 am */
    @EventListener(ContextRefreshedEvent.class)
    public void doEventManager(ContextRefreshedEvent contextRefreshedEvent) {
        final ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        if (applicationContext.getParent()==null) {// Indicates the root context object
            System.err.println("Container started complete");
            if(applicationContext.getClass().equals(AnnotationConfigApplicationContext.class)){
                // Close the container here, hahaha, am I perverted((AnnotationConfigApplicationContext)applicationContext).close(); }}}}Copy the code
  • ContextStartedEvent

Triggered when the container is started, by calling the start method, indicating that all Lifecycle beans have received the start signal.

  • ContextStoppedEvent

Lifecycle beans will be triggered when the container stops, i.e. by calling the Stop method, indicating that all Lifecycle beans have been shown to receive the stop signal and closed containers can be started by the start method.

  • ContextClosedEvent

Published when the container is closed, that is, the close method is called to indicate that all singleton beans are destroyed and the closed container cannot be restarted or refreshed.

  • RequestHandledEvent

Also valid when using Spring’s DispatcherServlet, i.e., when a request has been processed;

2.3) Event listeners

Spring provides two ways to implement listeners: interface-based and annotation-based.

  • Based on the interface

Implement the ApplicationListener interface, which requires a generic object, which is your custom or system-defined event object; Implement the onApplicationEvent method of the interface;

  • Based on the annotation

There is no need to implement any interface or class, just add the @EventListener annotation to the corresponding processing method. Note that the method supports only one parameter at most. This parameter is the type of the event object configured in the @EventListener annotation, or no parameter at all.

Either way, the effect is the same, and it depends on your architectural design, custom, and specification to use one or the other: whether interface – or annotation-based, your event listener must be able to be scanned and recognized by Spring as a managed Bean. PS: BEFORE, I had a friend who changed all kinds of postures just because of this problem, debugging for a whole day without success, and told me that he found a major BUG in Spring. He was so proud that I couldn’t laugh or cry. And then… There is no then;

2.4) Work processing process

Spring’s event handling mechanism is an implementation of the observer pattern. In addition to publishers and listeners, Spring also has EventMultiCaster, a role that forwards events to corresponding listeners. The general work process is as follows:

EventMultiCaster maintains all the listeners, which match the event type and send the information to the corresponding event receiver after the event publisher publishes the event, and the event receiver completes the corresponding logical processing.

Three, the implementation principle

Said principle, must be from the method of core, here will be from org. Springframework. Context. Support. AbstractApplicationContext# refresh methods, by the way, before the review content; If you are not sure about the content, refer to the previous chapter;

3.1) Prepare the context — the prepareRefresh method

/** * Prepare this context for refreshing, Setting its startup date and * active flag as well as performing any initialization of property sources. Is the first step in the Spring core method, doing some initialization of the container environment */
protected void prepareRefresh(a) {
   // Switch to active.
   this.startupDate = System.currentTimeMillis();
   /* * The system shutdown status is set to: Not disabled * */
   this.closed.set(false);
   /* * System active state is set to active state; The getBean method of this class has a statement of judgment method logic that asserts whether the container is active or not: The assertBeanFactoryActive method checks for closed and active * throws an exception if active==false * throws an exception if active==true and Closed == True * If the container is not started, you cannot call getBean to get the Bean. That is, to use getBean, you must first execute refresh(); * * /
   this.active.set(true);

   if (logger.isInfoEnabled()) {
      logger.info("Refreshing " + this);
   }

   /* * Many people on the Internet say that this method does not work, here is the same answer, Spring is an ecology, please do not treat it with a general view, ok? * Because it is an ecology-level existence, the consideration must be sufficient in many cases; * This method must be left to a subclass to implement; It is also an extensible interface for developers to define some mandatory rule specifications; * For example, if we write A class that overrides the initPropertySources method and sets the value of an environment variable to A *, it will throw an exception if there is no value in my environment variable. Do you need to go through the developer's code line by line? * /
   initPropertySources();

   /* * is used to verify the value of the environment variable on which the container must be started. * this method is usually used in conjunction with the initPropertySources method above
   getEnvironment().validateRequiredProperties();

   /* * Create an early event listener object * The early listener object is used in Spring to accommodate some of its own events; * If you register in an earlier time listener, there is no need to use the publishEvent method to trigger; Spring automatically publishes * this is just a collection initialization, you can add */ yourself
   if (this.earlyApplicationListeners == null) {
      this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
   }else {
      // Reset local application listeners to pre-refresh state.
      this.applicationListeners.clear();
      this.applicationListeners.addAll(this.earlyApplicationListeners);
   }

   /* * Create a container to hold a collection of long-anticipated events * what are early events? * is when our event listener is not registered with the multicast is called early event */
   this.earlyApplicationEvents = new LinkedHashSet<>();
}
Copy the code

We declare an early event listener and event. We don’t need the developer to manually call the publishEvent method to publish the event. Spring publishes the event. Big ye don’t worry, take your time, look down;

3.2) Bean factory property population-prepareBeanFactory method

/** * populates our bean factory with internal properties *@param beanFactory the BeanFactory to configure
 */
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   // Set the bean factory class loader to the current application loader
   beanFactory.setBeanClassLoader(getClassLoader());
   / / for bean factory set our standard SPEL StandardBeanExpressionResolver expression parser object
   beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
   // Set a propertityEditor property resource editor object for our bean factory (for later use in assigning bean objects)
   beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

   / * * registered a complete rear ApplicationContextAwareProcessor processor used to handle ApplicationContextAware interface callback method * note here is Aware, this word in the Bean's statement cycle, After the Bean is initialized, a bunch of Aware will be called. Here is one of the Aware called. * note: In the org. Springframework. Beans. Factory. Support. AbstractAutowireCapableBeanFactory. DoCreateBean * BeanNameAware, BeanClassLoaderAware, BeanFactoryAware *, BeanNameAware *, BeanClassLoaderAware *, BeanFactoryAware *, BeanNameAware *, BeanClassLoaderAware * * the answer lies in the following ApplicationContextAwareProcessor this object, the object, call the other Aware; If you're interested, you can go inside the object; * ApplicationContextAwareProcessor is a rear processor and post processor and post processor; Say important things three times; * is called before initialization in the Bean's declaration cycle; * * /
   beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));


   /** * When Spring registers Applie onContextAwareProcessor, the Aware class that is invoked indirectly in invokeAwarelnterfaces is not a normal bean. * such as ResourceLoaderAware, ApplicationEventPublisher, then of course need to do the bean in the Spring dependency injection to ignore them. * and ignoreDependencyInterface role is in the * /
   beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
   beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
   beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
   beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
   beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
   beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

   /** * When a dependency resolution is registered, such as when a beanFactory.class resolution dependency is registered, an instance of BeanFactory will be injected when a BeanFactory property is detected. * /
   beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
   beanFactory.registerResolvableDependency(ResourceLoader.class, this);
   beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
   beanFactory.registerResolvableDependency(ApplicationContext.class, this);

   / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * to highlight the * * * * * * * * * * * * * * * * * * * * * * * * * * * * register an event listener rear detector processor * ApplicationListenerDetector this object parses the interface way listeners * in postProcessAfterInitialization method for processing; * This post-processor is executed after the Bean life cycle is initialized; * * /
   beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

   // Handles aspectJ
   if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
      beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
      // Set a temporary ClassLoader for type matching.
      beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
   }

   // The beans inside the bean factory are registered
   if(! beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {/ / environment
      beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
   }
   if(! beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {// Environment system properties
      beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
   }
   if(! beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {// System environmentbeanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment()); }}Copy the code

Here to pay special attention to ApplicationListenerDetector this object, the object is a rear Bean processors (old routines and post processor, Spring is done through the post processor of decoupling and integration logic).

This post-processor is dedicated to parsing event listeners in the manner of the processing interface;

3.3) create multicast — initApplicationEventMulticaster method

Highlight: This multicast is the core object that Spring uses to distribute notifications

/** * A new multicast is assigned to our applicatoinContext object from the bean factory or directly displayed. The typical design pattern is observer mode@see org.springframework.context.event.SimpleApplicationEventMulticaster
 */
protected void initApplicationEventMulticaster(a) {
   // Get our bean factory object
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   / / it in the container is not our applicationEventMulticaster multicast application components
   if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
      // Call our getBean directly to get the value assigned to our applicationContext object
      this.applicationEventMulticaster =
            beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
      if (logger.isDebugEnabled()) {
         logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); }}// If the container does not have one
   else {
      / / spring ioc showed new a SimpleApplicationEventMulticaster objects stored in applicatoinContext object
      this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
      // And inject into the container
      beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
      if (logger.isDebugEnabled()) {
         logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
               APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
               "': using default [" + this.applicationEventMulticaster + "]"); }}}Copy the code

The “EventMulticaster” is responsible for distributing notifications when the publishEvent is called in development to trigger an event; In other words, the multicast object must hold all known sets of event listeners; Multicast can be thought of as the “master of the house” of event listeners;

< span style = “box-sizing: border-box; color: RGB (51, 51, 51); line-height: 22px; font-size: 14px! Important; word-break: inherit! Important;”

3.4 register event listeners on multicast listeners

Highlight: register the listener to the multicast device you created earlier, that is, give the listener to the manager (Manager Li, I gave Chen to you);

protected void registerListeners(a) {
   / * * to get all the listeners in the container object (the finished object) * this set must be empty inside, unless you call annotationConfigApplicationContext. AddApplicationListener (your family custom listener) 】; * * /
   for(ApplicationListener<? > listener : getApplicationListeners()) {// Register the listeners one by one to our multicast
      getApplicationEventMulticaster().addApplicationListener(listener);
   }

   /* * Get the listener object in the bean definition * bean definition, bean definition, bean definition; Say important things three times; * Note: this is a listener of type ApplicationListener; This is where the interface type listener is added to the multicast. * * /
   String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true.false);
   // Register the listener name with our multicast
   for (String listenerBeanName : listenerBeanNames) {
      getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
   }

   /* * Get our early events here * what we get here is the * */ that was injected when the prepareRefresh method was executed in the first step in the Refresh () method
   Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
   / / backhand is terribly cruel to empty off (true TMD as rude), which can be concluded that the early events in org. Springframework. Context. Support. AbstractApplicationContext. The refresh method will be executed only once
   this.earlyApplicationEvents = null;
   if(earlyEventsToProcess ! =null) {
      /* * Broadcast early events via multicast * this is why early events are pushed first * */
      for(ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster().multicastEvent(earlyEvent); }}}Copy the code

Here are two things to note:

  • 1) Add the interface type listener to the multicast;
  • 2) Broadcast early registration events;

Is the listener for the interface type registered here? Ha ha ha ha, the answer is no, 4.2 the content of the review, we are not in the Bean factory added a rear ApplicationListenerDetector type of processor? This is the listener used to resolve the interface mode, which is executed after initialization of the Bean life cycle;

public Object postProcessAfterInitialization(Object bean, String beanName) {
   /* * If it is an implementation of the ApplicationListener interface, the following processing is performed
   if (bean instanceof ApplicationListener) {
      // potentially not detected as a listener by getBeanNamesForType retrieval
      Boolean flag = this.singletonNames.get(beanName);
      if (Boolean.TRUE.equals(flag)) {
         /* * Singleton bean (top level or inner): register on the fly * */
         this.applicationContext.addApplicationListener((ApplicationListener<? >) bean); }else if (Boolean.FALSE.equals(flag)) {
         if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
            // inner bean with other scope - can't reliably process events
            logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
                  "but is not reachable for event multicasting by its containing ApplicationContext " +
                  "because it does not have singleton scope. Only top-level listener beans are allowed " +
                  "to be of non-singleton scope.");
         }
         this.singletonNames.remove(beanName); }}return bean;
}
Copy the code

This is where the Bean is validated and added to the multicast after successful validation;

[Notice] The process of adding a registerListeners to a multicast process Are the Spring developers out of their heads? This is not a problem, but rather an indication of the rigor of Spring developers; Instead, it is a solution to the problem that real CRUDS miss when developing their business using “Lazy” listeners. Because lazy-loaded bean-definition objects do not create beans when the container is initialized, the Bean’s post-processor is invoked during Bean creation; RegisterListeners are therefore handled to prevent omissions due to lazy loading (lazy-loaded listeners are added by name);

3.5) Annotation style listener handling

When was the annotation version of the listener injected? Go back to the previous chapter, remember where the Genesis object was loaded? (Do you still remember the summer rain lotus beside the Qingming River??) ; Several Genesis objects are dedicated listeners for parsing annotated versions;

Notice what the previous chapter said: org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.bean S. actory. Support. BeanDefinitionRegistry, Java. Lang. Object) this method to add a lot of genesis of the objects;

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
      BeanDefinitionRegistry registry, @Nullable Object source) {
   / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * to ignore irrelevant code * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
   / * * process monitoring method of annotation parser EventListenerMethodProcessor * specialized for processing @ EventListener * /
   if(! registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(EventListenerMethodProcessor.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
   }

   /* * register the EventListener factory * for handling @eventlistener */
   if(! registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(DefaultEventListenerFactory.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
   }

   return beanDefs;
}
Copy the code

Here EventListenerMethodProcessor object is a Aware, at the same time, pay attention to the SmartInitializingSingleton this interface, This interface can be in org. Springframework. Context. Support. AbstractApplicationContext# finishBeanFactoryInitialization instantiate all the rest of the singleton Bean method Called after all singleton beans have been created; org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

@Override
public void preInstantiateSingletons(a) throws BeansException {
   / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * to ignore irrelevant code * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
   // The name of the existing bean........... Up to this point all singleton beans have been logged into the cache
   for (String beanName : beanNames) {
      // Get all objects from the singleton cache pool
      Object singletonInstance = getSingleton(beanName);
      / / determine whether the current bean SmartInitializingSingleton interface is realized
      if (singletonInstance instanceof SmartInitializingSingleton) {
         / * * here is a genesis EventListenerMethodProcessor object starts * for analytical processing @ EventListener * * /
         final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
         if(System.getSecurityManager() ! =null) {
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
               smartSingleton.afterSingletonsInstantiated();
               return null;
            }, getAccessControlContext());
         }
         else {
            / / trigger afterSingletonsInstantiated instantiatedsmartSingleton.afterSingletonsInstantiated(); }}}}Copy the code

Here also interested in children’s shoes can be further with the source code;

Note: SmartInitializingSingleton is also a development point of the Spring, pay attention to this SmartInitializingSingleton call time;

Asynchronous and synchronous event processing

Spring supports both synchronous and asynchronous event processing. Usually, the event is executed synchronously, and there are two ways to execute the event asynchronously.

  • 1) Annotation method

Add the @enableAsync annotation to the configuration class and the @async annotation to the listener execution method;

  • 2) Manual configuration
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(a){
    final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
    // If TaskExecutor is set, multi-threaded asynchronous processing will be used
    simpleApplicationEventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
    return simpleApplicationEventMulticaster;
}
Copy the code

OK! That’s it for now! These two days more strange monsters, headache;