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 😋