Using Spring Application Events for Synchronous and Asynchronous Business Logic
This article demonstrates how to leverage Spring's ApplicationEvent mechanism to decouple business logic, showing both synchronous and asynchronous event publishing, custom event definitions, listener implementations, unit testing, and enabling async processing with @EnableAsync and @Async annotations.
In real-world business development, complex logic often involves a core operation plus many sub‑tasks, which can lead to long, tightly‑coupled code. Some tasks, such as sending emails or SMS, do not need to complete within the same request, and using a heavyweight message queue may be unnecessary.
Spring Event (Application Event) provides a lightweight observer‑pattern solution. After a bean finishes its work, it can publish an event that other beans listen to, allowing synchronous or asynchronous handling without tight coupling.
Custom Event Definition
import lombok.Data;
import lombok.AllArgsConstructor;
import org.springframework.context.ApplicationEvent;
/**
* Custom event carrying order information.
*/
@Data
@AllArgsConstructor
public class OrderProductEvent extends ApplicationEvent {
private String orderId;
public OrderProductEvent(Object source, String orderId) {
super(source);
this.orderId = orderId;
}
}Listener Implementation (Synchronous)
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<OrderProductEvent> {
@SneakyThrows
@Override
public void onApplicationEvent(OrderProductEvent event) {
String orderId = event.getOrderId();
long start = System.currentTimeMillis();
Thread.sleep(2000); // simulate processing
long end = System.currentTimeMillis();
log.info("{}: validate order price took {} ms", orderId, (end - start));
}
}Publisher (Service Layer)
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;
public String buyOrder(String orderId) {
long start = System.currentTimeMillis();
// 1. query order details (omitted)
// 2. validate price synchronously
applicationContext.publishEvent(new OrderProductEvent(this, orderId));
// 3. async tasks such as sending SMS/email
applicationContext.publishEvent(new MsgEvent(orderId));
long end = System.currentTimeMillis();
log.info("All tasks completed, total time: {} ms", (end - start));
return "Purchase successful";
}
}Asynchronous Listener
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("Sending SMS");
log.info("Sending email");
Thread.sleep(4000); // simulate async work
long end = System.currentTimeMillis();
log.info("{}: SMS & email took {} ms", orderId, (end - start));
}
}Enable Asynchronous Execution
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);
}
}Unit Test
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");
}
}Running the test shows the synchronous listener logging the price‑validation duration (~2 seconds) and the asynchronous listener logging SMS/email sending after the main thread finishes, demonstrating how Spring Event can simplify both sync and async workflows.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
