Spring extension principle event listener source level tutorial

The opening

Hello, everyone, I am the dust heart, a computer every day in front of the dripping men! Speaking of Spring, everyone is familiar with it, but there is still a long way to go if you want to master the various design principles of Spring. Today we’ll look at event listening in The Spring extension principle. Event monitoring is widely used in real development, so if you don’t know, then learn about it

Note: the author’s code versions all use [4.3.12.RELEASE]

Maven depends on:

        <! -- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.12. RELEASE</version>
        </dependency>
Copy the code

System event distribution [source code analysis]

A preliminary study on

We write a listener of our own that listens for events in the container. The actual code is as follows:

/** * adds the Component annotation to the container */
@Component
public class MyActionListener implements ApplicationListener<ApplicationEvent> {

    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("Received event :"+ event); }}Copy the code

Run the code and view the console:

You can see from the console that it is called as soon as the container is startedonApplicationEventMethod, triggerApplicationEventEvents.

Strange, we didn’t publish any events, why would an event trigger? The reason is that the IOC container publishes some basic events as soon as it is started, informing the user and making it easier for the user to handle them. Let’s click on the source code:

Delve into

1) this is we write start classes, we adopt AnnotationConfigApplicationContext annotations to start the IOC container

public class Application {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        // Prints the names of the beans in all containers
        String[] names = context.getBeanDefinitionNames();
        for(String name : names) { System.out.println(name); }}}Copy the code
  1. trackingAnnotationConfigApplicationContextGo in, find the following methods:
	public AnnotationConfigApplicationContext(Class
       ... annotatedClasses) {
                // Call the constructor
		this(a);// Register some bean definition information according to annotations
		register(annotatedClasses);
                // Refresh the container
		refresh();
	}

Copy the code

The first two steps are all registry definitions and so on, with no actual creation, so we assume that event publishing is called when the container is refreshed

  1. trackingrefresh()Method, which is found to be the core of the IOC container
	@Override
	public void refresh(a) throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throwex; }}}Copy the code

We saw initApplicationEventMulticaster focus (); Initialize event dispatchers and registerListeners(); Register listener methods

Event dispatchers and listeners are ready, so dispatch events must come later.

  1. trackingfinishRefresh();Method goes in, and sure enough, it’s called herepublishEventreleasedContextRefreshedEventEvent. And that’s what our console prints!
	/**
	 * Finish the refresh of this context, invoking the LifecycleProcessor's
	 * onRefresh() method and publishing the
	 * {@link org.springframework.context.event.ContextRefreshedEvent}.
	 */
	protected void finishRefresh(a) {
		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// Publish the final event. Here a ContextRefreshedEvent event is published
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}
Copy the code
  1. In addition to the system events we just listened for, there are several other system events:

We just used the superclass event, the system default distribution is subclass ContextRefreshedEvent events. Refer to the version definitions in the opening section for Spring versions

Other events are defined by the system and can be used directly. Now we focus on the distribution and listening of custom events!

Custom event distribution [code combat]

  1. Write your own events

If we want to publish events ourselves, we can simply write a custom event class that inherits ApplicationEvent, grab the IOC container, and call the dispatch method

// Here we write a custom event
public class MyApplicationEvent extends ApplicationEvent {

    /**
     * Create a new ApplicationEvent.
     *
     * @param source the component that published the event (never {@code null})
     */
    public MyApplicationEvent(Object source) {
        super(source); }}Copy the code
  1. Active dispatch defines events

Dispatches events using the context’s publishEvent method

public class Application {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println("Publish Event: MyApplicationEvent");
        context.publishEvent(new MyApplicationEvent("Event MyApplicationEvent"));

        System.out.println("Publish Event: MyApplicationEvent2");
        context.publishEvent(new MyApplicationEvent("Event MyApplicationEvent2")); }}Copy the code
  1. Listen for custom events
@Component
public class MyActionListener implements ApplicationListener<MyApplicationEvent>{

    public void onApplicationEvent(MyApplicationEvent event) {
        /** * here we listen for our own event, * when this event is dispatched, this method will fire */
        Object source = event.getSource();
        System.out.println("Monitor event :"+source); }}Copy the code

4) Run the container and view the result

As you can see, we post our own events, and we listen for them. Release a few times to deal with a few times, is not very sour cool?

How does the system send defined events? 【 Source code analysis 】

In the previous section, we demonstrated how to write custom events, dispatch them, and listen on them. So the events we write, how does Spring sense and help us distribute them? In the “System Event Dispatch” section, we saw these two methods during container initialization: if you forget, go back to the “System Event dispatch” section above and look again!

 initApplicationEventMulticaster(); // Initialize the event dispatcher
Copy the code
 registerListeners();// Register the listener
Copy the code

Initialize the dispatcher

Distributed initialization, get the beanFactory first, then from the local container distributed lookup have created good, if not registered to create a singleton SimpleApplicationEventMulticaster distributing device into the container

	protected void initApplicationEventMulticaster(a) {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); }}else {
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
			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

Register listeners

  1. The container takes all listeners and iterates through them to add them to the dispatcher’s listener List

  2. Get the names of all listeners according to the ApplicationListener type and store them in the dispatcher’s Set collection

	protected void registerListeners(a) {
		// Register statically specified listeners first.
		for(ApplicationListener<? > listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); }// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let post-processors apply to them!
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true.false);
		for(String lisName : listenerBeanNames) { getApplicationEventMulticaster().addApplicationListenerBean(lisName); }}Copy the code

Distributed event

The dispatcher, listener is now ready to trigger the dispatcher when we call the publish event method

	@Override
	public void publishEvent(ApplicationEvent event) {
		Assert.notNull(event, "Event must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Publishing event in " + getDisplayName() + ":" + event);
		}
		getApplicationEventMulticaster().multicastEvent(event);
		if (this.parent ! =null) {
			this.parent.publishEvent(event); }}Copy the code
  1. Distributed getApplicationEventMulticaster () to get the event

  2. MulticastEvent () publishes the event, looks at the source code, gets all the listeners corresponding to the event, and then starts the thread to execute the invokeListener

	@Override
	public void multicastEvent(final ApplicationEvent event) {
		for (finalApplicationListener<? > listener : getApplicationListeners(event)) { Executor executor = getTaskExecutor();if(executor ! =null) {
				executor.execute(new Runnable() {
					@Override
					public void run(a) { invokeListener(listener, event); }}); }else{ invokeListener(listener, event); }}}Copy the code
  1. invokeListener()Execute the listener, and here we clearly see that the listener executesonApplicationEventMethod, that’s what we override when we customize our listeners.
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
   ErrorHandler errorHandler = getErrorHandler();
   if(errorHandler ! =null) {
      try {
         listener.onApplicationEvent(event);
      }
      catch(Throwable err) { errorHandler.handleError(err); }}else{ listener.onApplicationEvent(event); }}Copy the code

Now that the source code analysis is over, you must have mastered the heart of Spring event listening.

High level usage @eventListener

In real development, Spring, for ease of use, simply uses the @EventListener annotation on the method you want to listen for events.

  1. Annotate the method you want to listen on
@Service
public class UserService {


    @EventListener(classes = MyApplicationEvent.class)
    public void listener(MyApplicationEvent event) {
        Object source = event.getSource();
        System.out.println("Note received event :"+source); }}Copy the code

2) Listen for method arguments, which are the events you want to pass

3) Start the container to see the effect. Very sour!

  1. Besides, the principle of

The principle is very simple, similar to what we analyzed before, except that the injection object uses annotation injection. The corresponding annotation is scanned first, then the method is called when the event is dispatched, and then the event is passed in.

Note: It is important to note that earlier versions of Spring do not provide this annotation. Not available in Sping4.1.12.

At the end

SpringBoot event listening mechanism is almost the same as Spring, so don’t worry. Master the source code, then all changes from its ancestor. I am dust heart, if this article is helpful to you, please give it a thumbs up. Public number (GitHub strictly selected), technical experience and easy to use black technology tools to share, go into the first point welfare list, massive welfare waiting for you.