preface

Event publishing is one of the most overlooked features of the Spring framework, but it’s actually a very useful feature. We use the event mechanism to decouple code that is coupled to each other within the same application, and we can combine events with Spring transactions to fulfill some of the business requirements in our work. Similar to Spring transactions, Spring transactions have programmatic transactions, declarative transactions, Spring events are also divided into programmatic events and declarative events, this article focuses on using annotations to achieve declarative event publishing and listening.

Spring Built-in Events

If you are familiar with Spring, you will be familiar with the built-in events provided by Spring.

Event Explanation
ContextRefreshedEvent Published when the ApplicationContext is initialized or refreshed
ContextStartedEvent Use ConfigurableApplicationContext interface on the start () method starts the ApplicationContext
ContextStoppedEvent In using ConfigurableApplicationContext interface on the stop () method to stop the ApplicationContext
ContextClosedEvent In using ConfigurableApplicationContext interface on the close () method or by shutting off the JVM release hook closed ApplicationContext
RequestHandledEvent A Web-specific event that tells all beans that HTTP requests have been served. This event is published after the request is completed. This event applies only to Web applications that use Spring’s DispatcherServlet.
ServletRequestHandledEvent Subclass of RequestHandledEvent to add servlet-specific context information

To tell you the truth, I copied it from the official website, because I am not familiar with the Spring source code, I tried to see it but…… It didn’t stick. Bypassing these, we’ll inherit ApplicationEvent to implement our own custom events.

Synchronization event

The so-called synchronous event is that the published event will not be handled by a new thread, but the caller and the original thread on the basis of business. For example, we now have a requirement to insert a purchase log for the user after the user submits the order. In the OrderService class

@Service @Slf4j public class OrderService { @Autowired private ApplicationEventPublisher applicationEventPublisher; // @transactional public void submit() {//... Log.info (".... before submitting order ") ); ApplicationEventPublisher. PublishEvent (new OrderCreateEvent (this, "test object")); // Publish event log.info(" before and after order submission....") ); }}Copy the code

The publishEvent method here needs to pass an event class object that inherits ApplicationEvent

public class OrderCreateEvent extends ApplicationEvent { @Getter public Object log; Public OrderCreateEvent(Object source, Object log) {super(source); /** * @param log @param source */ this.log = log; }}Copy the code

We can encapsulate the parameters that the event passes in the member variable of the OrderCreateEvent class by simply writing a log property.

The next thing to do is to listen for this event. Use @eventListener in OrderLogService to annotate the listening method. By default, it will listen for events of type Class on the method parameter object. You should use the @eventListener classes property to specify the types of events to listen to.

@Service @Slf4j public class OrderLogService { @EventListener public void listen(OrderCreateEvent event) { Log.info (" order creation event received: {}", event.getlog ()); try { Thread.sleep(10000); } catch (InterruptedException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace(); }}}Copy the code

Let the thread sleep for 10 seconds in the listener method to verify event synchronization (of course you can also print out the current thread ID to verify)

.... before submitting your order Order creation event received: before and after the test object submits the order....Copy the code

Observing the console logs, it can be concluded that the event is synchronous, and the original service must wait for the event to be processed before continuing to perform services.

Asynchronous events

Then you may have found the implementation scheme is not so good, because the behavior of create log actually does not belong to the order submission, that is to say every time orders submitted results will be released by the events performed to response, and submitted by the business events and released the order not directly to success or failure at the same time at the same time. If other events such as sending an SMS to the user after an order is created are added as the business expands, the response time of the interface will become longer and the throughput will decrease, which will undoubtedly affect the user experience. So we can consider using asynchronous events.

Implementing asynchronous events is as simple as adding @enableasync to the SpringBoot boot class to EnableAsync, and then adding the @async annotation to the listener methods.

@EventListener
@Async
public void listen(OrderCreateEvent event) {
    //...
}
Copy the code

The execution of the listening method is thus asynchronous and does not affect the throughput of the original order submission interface.

Bind the event to a transaction

As you may have noticed, the above asynchronous event solves the interface throughput problem, but it creates another problem. What if the order submission business fails? Because an asynchronous event listener throws an Exception, it is not propagated to the caller. In other words, if the above order submission business reported an error, our logging event and SMS event would still be executed. This problem is much more serious than the interface throughput problem, so Spring allows us to bind the event to the transaction.

Is also very simple to use, just need to original listener annotation @ EventListener for @ TransactionalEventListener can.

@TransactionalEventListener
public void listen(OrderCreateEvent event) {
   //...
}
Copy the code

We can use the phase attribute of this annotation to determine at which stage of the original caller’s transaction the listener should start execution. There are four values

  • TransactionPhase.BEFORE_COMMIT – Before the transaction is committed
  • TransactionPhase.AFTER_COMMIT — After the transaction is committed (default)
  • Transactionphase. AFTER_ROLLBACK — After the transaction rollback
  • TransactionPhase.AFTER_COMOLETION — After the transaction has completed (including transaction commit and rollback)

It is worth noting that the use of the @ TransactionalEventListener listener, the event the caller must have the transaction, otherwise will not be executed. This means that the @transactional annotation alone does not make it necessary to have database-related integration because @Transactional also makes transactions not automatically committed by changing the auto_commit attribute of the database connection to false.

Conditional event

@ EventListener and @ TransactionalEventListener condition attributes, can be used to judge the parameters of the events executed when a certain condition to monitor events. Such as:

@eventListener (condition = "event.log == 'test object 2'") public void Listen (OrderCreateEvent Event) {//... }Copy the code

If the publication event passes a parameter value other than that specified in the condition, the listener will not execute.

Sequence of events

We can control the Order in which listeners are executed with the @order annotation. The smaller the value of the annotation, the higher the Order in which listeners are executed. However, it is not recommended to use it to control order in asynchronous events, as it makes little sense.

@eventListener @order (1) // This listener will execute first public void Listen (OrderCreateEvent Event) {//... } @eventListener @order (2) public void listen2(OrderCreateEvent event) {listen2(OrderCreateEvent event) {//... }Copy the code

Event and message queues

As you may have noticed, Spring’s event publishing and message queuing have a lot in common. So let’s compare the similarities and differences

The same

  • Decouple: Decouple where the code is coupled
  • Asynchronous: Can asynchronously execute a certain thing that does not belong to the current method business, improving the system throughput

The difference between

  • Scope of use: Events can only be used within an application and cannot be used across systems, whereas message queues can be used across multiple systems.
  • Peaking ability: Event peaking is very limited, equivalent to open multiple threads, this is not good more threads will consume server resources,. In message queue peaking, the third party middleware is introduced, which can effectively peak traffic and carry out high concurrent request volume.
  • Similarly, events will bring fewer problems due to the limited range of use, and message queues will introduce more problems due to the wide range of use. It is necessary to ensure the high availability of message queues and solve problems such as message reliability and idempotent.

conclusion

Spring event publishing is suitable for small projects, but business with high concurrent traffic still needs professional messaging middleware to support it.

conclusion

If this post helped you, be sure to like and follow. Your support is my motivation to continue my creation!