Purpose of Spring event source

preface

Spring is already very familiar with spring. Spring container state events are also daily functions, which are often used for decoupling. However, sometimes events will be repeatedly monitored. At this time, they need to be handled. source is also used for special purposes.

1. demo

Construct a Spring boot application and write a listener.

@SpringBootApplication
public class EventMain {

    public static void main(String[] args) {
        ConfigurableApplicationContext currentContext = SpringApplication.run(EventMain.class, args);
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.setParent(currentContext);
        context.refresh();
    }


}

@Component
public class SpringEventListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("hello--------------" + event.getSource());
    }
}

After startup, monitor twice.

 

2. Source code analysis

Spring event sending source code

	public void publishEvent(ApplicationEvent event) {
		publishEvent(event, null);
	}

Further positioning

	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

		// Decorate event as an ApplicationEvent if necessary
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}

		// Multicast right now if possible - or lazily once the multicaster is initialized
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
            //Container event handling code
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

        //This is very special. The parent will also send events
		// Publish event via parent context as well...
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}

  The parent will also send the event of the current container. If the parent has a parent, it will become a Dalian string. If the listener is defined in the parent, it will listen multiple times

	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        //spring handles synchronization or asynchrony through the Executor
		Executor executor = getTaskExecutor();
        //Get listener, for processing
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

Just like executing code synchronously and asynchronously, the only difference is whether the thread pool executes or not

Whether to define error handling. After all, event handling can customize exception handling logic

	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);
		}
	}

There is no definition here

	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			String msg = ex.getMessage();
			if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
					(event instanceof PayloadApplicationEvent &&
							matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception.
				Log loggerToUse = this.lazyLogger;
				if (loggerToUse == null) {
					loggerToUse = LogFactory.getLog(getClass());
					this.lazyLogger = loggerToUse;
				}
				if (loggerToUse.isTraceEnabled()) {
					loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

Observer mode, call onApplicationEvent method, and the source code analysis ends. From the principle analysis, Spring will pass events upward. If the current Spring container has a parent, such as Feign of Spring Cloud, the events will be consumed repeatedly.

3. Solutions

In fact, there is not only one way to solve this problem. If you customize an event, you can solve it according to some conditions, such as whether the current container has a tag. However, if it is Spring's own event or we don't want to define a tag, you can also judge it only through the Spring container. Generally speaking, we want Spring events to be consumed only in the current container. Then you only need = = to judge, so it is recommended to put the SpringApplicationContext in the source. Of course, this is generally the case 😄.

@Component
public class SpringEventListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (applicationContext == event.getApplicationContext())
            System.out.println("hello--------------" + event.getSource());
    }
}

Because ApplicationContext is a reference, the memory address is unchanged, and it is a single instance, the judgment is accurate, and only = =.  

summary

The transmission of Spring events to the parent is a feature of Spring, but sometimes our consumption events are not idempotent. At this time, we need to avoid this problem by various means. The author provides a relatively simple method and the code is relatively simple 😋

Keywords: Java Spring Spring Boot Back-end architecture

Added by kappaluppa on Mon, 01 Nov 2021 14:44:43 +0200