SoFunction
Updated on 2025-04-14

Detailed explanation of the three application event handling mechanisms in SpringBoot

introduction

In project development, loosely coupled design between components is crucial. The application event processing mechanism is an implementation of the observer pattern, allowing the system to realize communication between components while maintaining module independence. SpringBoot continues and enhances the event mechanism of the Spring framework, providing a variety of flexible event processing methods, allowing developers to efficiently implement message notification and state change processing within the system.

The advantage of an event-driven architecture is that it improves the scalability and maintainability of the system. When an action is triggered, the relevant events are published, and different listeners can respond to these events according to their own needs, without interfering with each other. This loosely coupled design allows us to add new features to the system without modifying existing code.

1. Basic concepts of Spring event mechanism

Before diving into various event handling mechanisms, it is necessary to understand several core components of Spring event mechanism:

  • ApplicationEvent: represents the event that occurs in the application and is the base class of all custom events
  • Event Publisher (ApplicationEventPublisher): Responsible for publishing events to the system
  • Event Listener (ApplicationListener): Listen to specific types of events and respond to them

Spring provides a built-in event notification mechanism where events can be sent from one Spring Bean to another without requiring them to directly refer to each other, thus achieving loose coupling. In SpringBoot, this mechanism is further simplified and enhanced, making event processing more convenient and powerful.

2. Method 1: Event listening based on ApplicationListener interface

1. Basic Principles

This is the most traditional way of handling events in the Spring framework. By implementing the ApplicationListener interface, you can create listeners that can respond to specific event types. When the matching event is published, the listener's onApplicationEvent method is automatically called.

2. Implementation steps

2.1 Custom events

First, we need to create a custom event class that inherits ApplicationEvent:

public class UserRegisteredEvent extends ApplicationEvent {
    
    private final String username;
    
    public UserRegisteredEvent(Object source, String username) {
        super(source);
         = username;
    }
    
    public String getUsername() {
        return username;
    }
}

2.2 Create an event listener

Implement the ApplicationListener interface and specify the type of event to be listened to:

@Component
public class UserRegistrationListener implements ApplicationListener<UserRegisteredEvent> {
    
    private static final Logger logger = ();
    
    @Override
    public void onApplicationEvent(UserRegisteredEvent event) {
        ("New user registration: {}, Source of the event: {}", 
                (), ().toString());
        
        // Process business logic, such as sending welcome emails, etc.        sendWelcomeEmail(());
    }
    
    private void sendWelcomeEmail(String username) {
        // Email sending logic        ("To the user {} Send a welcome email", username);
    }
}

2.3 Publish events

Use ApplicationEventPublisher to publish events:

@Service
public class UserService {
    
    private final ApplicationEventPublisher eventPublisher;
    
    @Autowired
    public UserService(ApplicationEventPublisher eventPublisher) {
         = eventPublisher;
    }
    
    public void registerUser(String username, String password) {
        // User registration business logic        ("Registered User: {}", username);
        
        // After successful registration, post an event        (new UserRegisteredEvent(this, username));
    }
}

3. Pros and cons analysis

advantage

  • Type-safe, compiler can detect type mismatch problems
  • Clear structure, clear relationship between listener and event
  • Comply with the principles of interface-oriented programming
  • It can easily implement generic listeners to handle a series of related events

shortcoming

  • You need to create a listener class for each event. When there are many event types, the code volume is large.
  • A single listener can only listen to one type of event
  • The code is relatively verbose, and the interface needs to be implemented and the method is covered.
  • The configuration is relatively cumbersome

4. Applicable scenarios

  • Type-safe event handling is required
  • The listener logic is complex and requires good encapsulation.
  • Already Spring Framework Migration Projects
  • Need to handle built-in events such as ContextRefreshedEvent, etc.

3. Method 2: Event listening based on @EventListener annotation

1. Basic Principles

Starting from Spring 4.2, an annotation-based event listening mechanism has been introduced. Any method can be marked as an event listener through the @EventListener annotation. This method simplifies the creation of listeners and no longer requires the implementation of the ApplicationListener interface.

2. Implementation steps

2.1 Custom events

We can use the UserRegisteredEvent we defined earlier, or create simpler event objects, or even normal Java objects (POJO):

// Use POJO as event objectpublic class OrderCompletedEvent {
    
    private final String orderId;
    private final BigDecimal amount;
    
    public OrderCompletedEvent(String orderId, BigDecimal amount) {
         = orderId;
         = amount;
    }
    
    // getters
    public String getOrderId() {
        return orderId;
    }
    
    public BigDecimal getAmount() {
        return amount;
    }
}

2.2 Create annotated listening method

In any Spring Bean, use @EventListener to annotate the tag method:

@Component
public class OrderEventHandler {
    
    private static final Logger logger = ();
    
    @EventListener
    public void handleOrderCompletedEvent(OrderCompletedEvent event) {
        ("Order completion: {}, Amount: {}", (), ());
        
        // Business logic after processing orders are completed        updateInventory(());
        notifyShipping(());
    }
    
    // Can also handle multiple different types of events in the same class    @EventListener
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        ("New user registration detected: {}", ());
        // Other processing logic    }
    
    private void updateInventory(String orderId) {
        // Update inventory logic        ("Update orders {} Stock of related products", orderId);
    }
    
    private void notifyShipping(String orderId) {
        // Notify the logistics department        ("Notify the logistics department to process the order: {}", orderId);
    }
}

2.3 Publish events

Also use ApplicationEventPublisher to publish events:

@Service
public class OrderService {
    
    private final ApplicationEventPublisher eventPublisher;
    
    @Autowired
    public OrderService(ApplicationEventPublisher eventPublisher) {
         = eventPublisher;
    }
    
    public void completeOrder(String orderId, BigDecimal amount) {
        // Order completion business logic        ("Complete the order: {}, Amount: {}", orderId, amount);
        
        // Order completion event        (new OrderCompletedEvent(orderId, amount));
    }
}

2.4 Conditional event monitoring

The @EventListener annotation also supports SpEL expressions for conditional filtering:

@Component
public class LargeOrderHandler {
    
    private static final Logger logger = ();
    
    // Only orders with an amount greater than 1,000 are processed    @EventListener(condition = "#(T().valueOf(1000)) > 0")
    public void handleLargeOrder(OrderCompletedEvent event) {
        ("Large orders detected: {}, Amount: {}", (), ());
        // Special handling of large orders        notifyFinanceDepartment((), ());
    }
    
    private void notifyFinanceDepartment(String orderId, BigDecimal amount) {
        ("Notify the financial department to pay attention to large orders: {}, Amount: {}", orderId, amount);
    }
}

3. Pros and cons analysis

advantage

  • Concise code, no need to implement interfaces
  • A class can handle multiple different types of events
  • Supports conditional filtering, high flexibility
  • You can use ordinary POJO as event object
  • Support method return value as new event publishing (event chain)

shortcoming

  • Method names are not constrained, which may cause inconsistent naming
  • Unable to implement beans that implement specific interfaces through type lookup

4. Applicable scenarios

  • Need to handle multiple events in a single class
  • The event logic is simple, and the pursuit of concise code is a scenario
  • Requires selective processing of events based on conditions

4. Method 3: Processing mechanism based on asynchronous events

1. Basic Principles

By default, Spring's event processing is synchronized, that is, the event publisher will wait for all listeners to complete processing before continuing to execute. For time-consuming operations, this can lead to performance issues. SpringBoot provides an asynchronous event processing mechanism, allowing event processing to be executed in a separate thread.

Asynchronous event processing requires two key steps: enable asynchronous support and mark the listener asynchronous.

2. Implementation steps

2.1 Enable asynchronous support

Add @EnableAsync annotation to the configuration class:

@SpringBootApplication
@EnableAsync
public class Application {
    public static void main(String[] args) {
        (, args);
    }
}

2.2 Configuring the asynchronous task executor (optional)

By default, Spring uses SimpleAsyncTaskExecutor to perform asynchronous tasks, but in actual project development, it is usually necessary to configure a custom task executor:

@Configuration
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        (5);
        (10);
        (25);
        ("Event-Async-");
        ();
        return executor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

2.3 Create an asynchronous event listener

You can use the first two methods to create a listener, just add the @Async annotation:

// Method 1: Asynchronous listener based on ApplicationListener interface@Component
@Async
public class AsyncEmailNotificationListener implements ApplicationListener<UserRegisteredEvent> {
    
    private static final Logger logger = ();
    
    @Override
    public void onApplicationEvent(UserRegisteredEvent event) {
        ("Asynchronously handle user registration events,Ready to send an email,Thread: {}", 
                ().getName());
        
        // Simulation time-consuming operation        try {
            (3000);
        } catch (InterruptedException e) {
            ().interrupt();
        }
        
        ("Asynchronous email sending is completed,user: {}", ());
    }
}

// Method 2: Asynchronous listener based on @EventListener annotation@Component
public class NotificationService {
    
    private static final Logger logger = ();
    
    @EventListener
    @Async
    public void handleOrderCompletedEventAsync(OrderCompletedEvent event) {
        ("Asynchronously handle order completion events,Ready to send notifications,Thread: {}", 
                ().getName());
        
        // Simulation time-consuming operation        try {
            (2000);
        } catch (InterruptedException e) {
            ().interrupt();
        }
        
        ("Asynchronous notification sending is completed,Order: {}", ());
    }
}

2.4 Using @TransactionalEventListener

SpringBoot also provides a special transaction-bound event listener that can control the relationship between event processing and transactions:

@Component
public class OrderAuditService {
    
    private static final Logger logger = ();
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    @Async
    public void auditOrderAfterCommit(OrderCompletedEvent event) {
        ("Asynchronous audit order after transaction submission: {}, Thread: {}", 
                (), ().getName());
        
        // Record audit logs and other operations        storeAuditRecord(event);
    }
    
    private void storeAuditRecord(OrderCompletedEvent event) {
        // Logic for storing audit records        ("Store orders {} Audit record", ());
    }
}

@TransactionalEventListener supports four transaction phases:

  • BEFORE_COMMIT: Before transaction commit
  • AFTER_COMMIT: After the transaction is successfully committed (default)
  • AFTER_ROLLBACK: After the transaction is rolled back
  • AFTER_COMPLETION: After the transaction is completed (regardless of commit or rollback)

3. Pros and cons analysis

advantage

  • Improve system response speed, the main thread does not need to wait for event processing to complete
  • Suitable for processing time-consuming operations, such as sending emails, push notifications, etc.
  • Can be integrated with transactions to control event processing timing
  • Flexible configuration of thread pools and optimize resource usage

shortcoming

  • Increase system complexity, debugging and tracking are difficult
  • Exception handling is more complicated and requires special attention
  • Resource management requires caution to prevent thread pool exhaustion

4. Applicable scenarios

  • Event processing includes time-consuming operations
  • Scenarios where the system requires high response time
  • Business operations that require integration with transactions
  • Scenarios where event processing does not affect the main process
  • Batch processing or background task scenarios

5. Comparison and selection of three event mechanisms

characteristic ApplicationListener @EventListener Asynchronous event mechanism
Implementation method Interface implementation Annotation method Interface or annotation +@Async
Code simplicity More verbose concise Depend on the basic mechanism
Type safety Strong type Depend on method parameters Same as the basic mechanism
flexibility medium high high
Handle multiple events One listener per type Multiple methods in one class Same as the basic mechanism
Conditional filtering Need to be programmed to implement Support SpEL expressions Same as the basic mechanism
Debugging difficulty Simple Simple More complex

6. Scenario example-user registration process

After the user successfully registers, multiple subsequent operations need to be performed, such as sending welcome emails, initializing user configurations, recording audit logs, etc.:

// Event objectpublic class UserRegistrationEvent {
    private final String username;
    private final String email;
    private final LocalDateTime registrationTime;
    
    // constructor and getter omitted}

// Event release@Service
public class UserServiceImpl implements UserService {
    
    private final UserRepository userRepository;
    private final ApplicationEventPublisher eventPublisher;
    
    @Autowired
    public UserServiceImpl(UserRepository userRepository, 
                           ApplicationEventPublisher eventPublisher) {
         = userRepository;
         = eventPublisher;
    }
    
    @Transactional
    @Override
    public User registerUser(UserRegistrationDto dto) {
        // Verify user data        validateUserData(dto);
        
        // Create a user        User user = new User();
        (());
        (());
        ((()));
        
        // Save the user        User savedUser = (user);
        
        // Publish registration events        (new UserRegistrationEvent(
                (), 
                (), 
                ()));
        
        return savedUser;
    }
}

// Asynchronous mail processing@Component
public class EmailService {
    
    @EventListener
    @Async
    public void sendWelcomeEmail(UserRegistrationEvent event) {
        ("Send a welcome email asynchronously: {}", ());
        // Email sending logic    }
}

// Audit logging@Component
public class AuditService {
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void logUserRegistration(UserRegistrationEvent event) {
        ("Record user registration audit log: {}, time: {}", 
                (), ());
        // Audit logging logic    }
}

// User initialization@Component
public class UserSetupService {
    
    @EventListener
    public void setupUserDefaults(UserRegistrationEvent event) {
        ("For new users {} Set the default configuration", ());
        // User configuration initialization logic    }
}

7. Summary

In practical applications, appropriate event processing mechanisms can be selected according to specific needs, and even different methods can be used in a mixed manner. No matter which method you choose, follow good design principles and best practices to build a high-quality enterprise application system.

Spring event mechanism can be used as a lightweight in-system communication solution. By combining message queues (such as RabbitMQ, Kafka, etc.), local events can be extended to a distributed environment to realize a cross-service event-driven architecture.

The above is the detailed explanation of the three application event processing mechanisms in SpringBoot. For more information about SpringBoot application event processing mechanisms, please pay attention to my other related articles!