Spring Event Tutorial: Synchronous and Asynchronous Usage for Decoupling Business Logic
This article demonstrates how to use Spring ApplicationEvent to decouple complex business logic, showing both synchronous and asynchronous event publishing, custom event creation, listener implementation, unit testing, and enabling async execution in a Spring Boot application.
In real-world business development, a single request may involve a core operation plus many sub‑operations, leading to long, tightly‑coupled code; some tasks such as sending emails or SMS do not need to be completed synchronously.
Spring Event (ApplicationEvent) implements the observer pattern, allowing a bean to publish an event after finishing its work and other beans to listen and react, thus reducing coupling without the heavyweight overhead of a message queue.
Spring Event Synchronous Usage
Define a custom event class that extends ApplicationEvent :
import lombok.Data;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;
@Data
@ToString
public class OrderProductEvent extends ApplicationEvent {
/** event payload */
private String orderId;
public OrderProductEvent(Object source, String orderId) {
super(source);
this.orderId = orderId;
}
}Implement a listener by either implementing ApplicationListener<OrderProductEvent> or using @EventListener :
import com.csp.mingyue.event.events.OrderProductEvent;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class OrderProductListener implements ApplicationListener
{
@SneakyThrows
@Override
public void onApplicationEvent(OrderProductEvent event) {
String orderId = event.getOrderId();
long start = System.currentTimeMillis();
Thread.sleep(2000); // simulate validation
long end = System.currentTimeMillis();
log.info("{}:validate order price took ({} ms)", orderId, (end - start));
}
}Spring Event Asynchronous Usage
Define another simple event for asynchronous tasks:
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class MsgEvent {
/** payload */
private String orderId;
}Use @EventListener together with @Async to process the event asynchronously:
import com.csp.mingyue.event.events.MsgEvent;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MsgListener {
@SneakyThrows
@Async
@EventListener(MsgEvent.class)
public void sendMsg(MsgEvent event) {
String orderId = event.getOrderId();
long start = System.currentTimeMillis();
log.info("send SMS");
log.info("send email");
Thread.sleep(4000); // simulate I/O
long end = System.currentTimeMillis();
log.info("{}:send SMS & email took ({} ms)", orderId, (end - start));
}
}Publisher (Service) Implementation
Inject ApplicationContext to publish events:
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private final ApplicationContext applicationContext;
/** place order */
public String buyOrder(String orderId) {
long start = System.currentTimeMillis();
// 1. query order details
// 2. validate price (synchronous)
applicationContext.publishEvent(new OrderProductEvent(this, orderId));
// 3. send notifications (asynchronous)
applicationContext.publishEvent(new MsgEvent(orderId));
long end = System.currentTimeMillis();
log.info("task completed, total time: ({}) ms", end - start);
return "purchase successful";
}
}Unit Test
Simple JUnit test to trigger the flow:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void buyOrderTest() {
orderService.buyOrder("732171109");
}
}Enable Asynchronous Execution
Add @EnableAsync to the Spring Boot main class:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@SpringBootApplication
public class MingYueSpringbootEventApplication {
public static void main(String[] args) {
SpringApplication.run(MingYueSpringbootEventApplication.class, args);
}
}Running the test shows synchronous validation taking ~2 seconds, asynchronous SMS/email sending taking ~4 seconds, and the overall request completing in about 6 seconds, while the async thread continues logging after the main thread finishes.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.