laitimes

Spring | Event listener applications and best practices

author:A programmer who loves to fish

introduction

In complex software development environments, communication and information exchange between components is particularly important. The Spring Framework, one of the most popular development frameworks in the Java world, provides a powerful event listener model that makes communication between components more flexible and decoupled. This article mainly discusses the principle, usage and application of Spring event listener in actual development, hoping to provide a practical reference for developers.

1.1 Introduction to Spring Event Listeners

The Spring event listener is a mechanism used in Spring applications to process events. Events typically represent changes in the state of the application, and listeners are responsible for responding to those changes. Through Spring's event listener, developers can realize the exchange of information between different components under the premise of decoupling, and improve the maintainability and extensibility of the code.

1.2 Purpose of the article

The purpose of this article is to deeply explore the basic principles of Spring event listeners, guide readers how to use listeners in actual development, and show the use scenarios and implementation methods of listeners through some specific examples. We will also take a closer look at the source code of the Spring listener to give you a deeper understanding of how it works. It is hoped that through this article, readers will be more proficient in using Spring event listeners to build flexible and maintainable applications.

All of the following examples have been uploaded to Github, and you can pull the project to run locally

Github example (if you are not familiar with Gradle, I recommend looking at my previous article): gradle-spring-boot-demo

Spring event listener principle

Understanding the principles of the Spring event listener is a prerequisite for using this mechanism effectively. This chapter takes a deep dive into the core components of Spring event listeners and how they work together.

2.1 Component Introduction

In Spring's event listener model, there are three core components involved: Event, Listener, and Event Publisher.

2.1.1 Event

An event is usually triggered by a specific action or state change. In Spring, custom events often need to inherit from the ApplicationEvent class. The event class contains the basic information of the event, such as the source of the event, the time of occurrence, and so on.

import org.springframework.context.ApplicationEvent;

public class CustomEvent extends ApplicationEvent {
    public CustomEvent(Object source) {
        super(source);
    }
}
           

2.1.2 Listener

Listeners are responsible for receiving and processing events. In Spring, a listener is usually a class that implements the ApplicationListener interface, and needs to define an onApplicationEvent method to handle events.

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
    @Override
    public void onApplicationEvent(CustomEvent event) {
        // 处理事件
        System.out.println("Received custom event - " + event);
    }
}
           

2.1.3 Event Publisher

The role of the event publisher is to notify all registered listeners of the event. In a Spring application, the ApplicationEventPublisher interface is responsible for publishing events.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
public class CustomEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doSomethingAndPublishAnEvent(final String message) {
        System.out.println("Publishing custom event.");
        CustomEvent customEvent = new CustomEvent(message);
        applicationEventPublisher.publishEvent(customEvent);
    }
}
           

2.2 Workflow

The basic workflow of the event listener model is as follows:

  1. The event source produces the event.
  2. The event publisher publishes events.
  3. The registered listener processes the event after receiving it. This model supports low-coupling interactions between components, making it more flexible and convenient for developers to develop.

How to use Spring listeners

Now that we understand the fundamentals and components of the Spring event listener, we'll take a closer look at how you can use it in your real world development. By defining events, creating listeners, and publishing events, we can exchange information between different components.

3.1 Defining Events

In Spring, we can define our own events by inheriting from the ApplicationEvent class. This class needs to contain all the information related to the event.

public class TestEvent extends ApplicationEvent {
    private String message;

    public TestEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}
           

In this example, we create an event class called TestEvent, which contains a message field of type string to pass information about the event.

3.2 Create a listener

Once the event is defined, we need to create a listener to handle the event. A listener is a class that implements the ApplicationListener interface, and you need to override the onApplicationEvent method to define the event processing logic.

@Component
public class TestEventListener implements ApplicationListener<TestEvent> {
    @Override
    public void onApplicationEvent(TestEvent testEvent) {
        // [3]在这里可以执行监听到事件后的逻辑, 监听到事件源,触发动作!
        System.out.println("监听到TestEvent:" + testEvent.getMessage());
    }
}
           

In this example, we define a listener, TestEventListener, that prints out the message from the received TestEvent event.

3.3 Publishing Events

Finally, we need to publish the event. The publishing of events is usually done by the event publisher ApplicationEventPublisher.

@Component
public class TestEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publish(String message) {
        // [2]使用publishEvent方法发布事件,事件源为TestEvent
        applicationEventPublisher.publishEvent(new TestEvent(this, message));
    }
}
           

In this example, the publishEvent method in the TestEventPublisher class creates and publishes a new TestEvent event. With these three steps, we can define, listen, and publish events in our Spring application. This not only facilitates the decoupling of components, but also enhances the maintainability and extensibility of the code.

3.4 Code Testing

@SpringBootTest
class GradleSpringBootDemoApplicationTests {


	@Autowired
	private TestEventPublisher testEventPublisher;

	@Test
	void contextLoads() {
		// [1] 发布事件
		testEventPublisher.publish("Hello, Spring!");
	}
}
           

The execution is complete, and the result is as follows:

Spring | Event listener applications and best practices

A handwriting example based on a listener design pattern

To gain a deeper understanding of Spring's listener pattern, let's write a simple example based on the listener design pattern, showing you how to design events, listeners, and how to publish events.

4.1 Design Objectives

We will create a simple user registration system. After the user has successfully registered, a registration event is published, which is listened to by the relevant listener and then performs actions such as sending a welcome email and logging it.

4.2 Implementation Steps

4.2.1 Defining Events

First, let's define an event where a user registers successfully. The event contains the basic information of the user.

public class UserRegisterEvent {
    private final String username;
    private final String email;

    public UserRegisterEvent(String username, String email) {
        this.username = username;
        this.email = email;
    }

    // Getters
}
           

4.2.2 Create a listener

Next, we create two listeners: one that sends the welcome email and the other that keeps a log of user registrations.

public class WelcomeEmailListener {
    public void sendWelcomeEmail(UserRegisterEvent event) {
        System.out.println("Sending welcome email to " + event.getEmail());
    }
}

public class UserRegisterLogListener {
    public void logUserRegister(UserRegisterEvent event) {
        System.out.println("Logging user register: " + event.getUsername());
    }
}
           

4.2.3 Publishing Events

Finally, we create a user registration service that publishes events after a user has successfully registered.

import java.util.ArrayList;
import java.util.List;

public class UserRegisterService {
    private final List<Object> listeners = new ArrayList<>();

    public void registerUser(String username, String email) {
        // 用户注册逻辑(略)
        System.out.println("User registered: " + username);
        
        // 发布事件
        UserRegisterEvent event = new UserRegisterEvent(username, email);
        for (Object listener : listeners) {
            if (listener instanceof WelcomeEmailListener) {
                ((WelcomeEmailListener) listener).sendWelcomeEmail(event);
            } else if (listener instanceof UserRegisterLogListener) {
                ((UserRegisterLogListener) listener).logUserRegister(event);
            }
        }
    }

    public void addListener(Object listener) {
        listeners.add(listener);
    }
}
           

We can add a main method to simulate the user's registration process and trigger the publication and listening of events.

public class Runner {
    public static void main(String[] args) {
        // 创建UserRegisterService实例
        UserRegisterService userRegisterService = new UserRegisterService();

        // 向UserRegisterService中添加监听器
        userRegisterService.addListener(new WelcomeEmailListener());
        userRegisterService.addListener(new UserRegisterLogListener());

        // 模拟用户注册
        userRegisterService.registerUser("JohnDoe", "[email protected]");
    }
}
           

When you run the main method, the UserRegisterService will execute the registration logic and then publish the UserRegisterEvent event, which the WelcomeEmailListener and UserRegisterLogListener listeners will capture and act accordingly.

Spring | Event listener applications and best practices

The result is as follows:

User registered: kfaino
Sending welcome email to [email protected]
Logging user register: kfaino
           

Interpretation of the source code of the Spring listener

In this chapter, we will explore the implementation details of Spring listeners to gain a deeper understanding of how Spring designs and implements event listeners.

5.1 ApplicationEvent和ApplicationListener

ApplicationEvent and ApplicationListener are the cornerstones of Spring's event listening mechanism.

5.1.1 ApplicationEvent

ApplicationEvent is the base class for all Spring events, and it inherits from java.util.EventObject. It contains the source of the event and the timestamp of the event.

public abstract class ApplicationEvent extends EventObject {
    private static final long serialVersionUID = 7099057708183571937L;
    private final long timestamp;

    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }

    public final long getTimestamp() {
        return this.timestamp;
    }
}
           

5.1.2 ApplicationListener

ApplicationListener is a generic interface for handling specific types of events. It contains a method onApplicationEvent that the user needs to implement to define the event handling logic.

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}
           

5.2 Publication of Events

ApplicationEventPublisher is the core interface for event publishing. It defines the publishEvent method, which is used to publish the event to all matching listeners.

public interface ApplicationEventPublisher {
    void publishEvent(ApplicationEvent event);
    void publishEvent(Object event);
}
           

ApplicationContext inherits from the ApplicationEventPublisher interface, so in a Spring container, you can use ApplicationContext directly to publish events.

5.3 Propagation of Events

In Spring, event propagation is implemented via the SimpleApplicationEventMulticaster class. This class has a multicastEvent method that passes the event to all matching listeners.

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        for (final ApplicationListener<?> listener : getApplicationListeners(event, eventType)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            } else {
                invokeListener(listener, event);
            }
        }
    }
}
           

In this method, getApplicationListeners is used to get all matching listeners, and then the invokeListener method is used to trigger those listeners.

5.4 Summary

By digging deeper into the source code of the Spring event listener, we can better understand how Spring implements event definition, publishing, and handling, which helps us to use this mechanism more effectively in real development.

Spring built-in events

The Spring framework itself provides some built-in events that represent some lifecycle stages or specific operations of a container that can help us better monitor and manage our applications.

6.1 ContextRefreshedEvent

The ContextRefreshedEvent event is triggered when the Spring container is initialized or refreshed, i.e. when all beans have been successfully loaded, the post-processor has been called, and all singleton beans have been pre-instantiated.

@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
    System.out.println("Context Refreshed: " + event.getTimestamp());
}
           

In SpringBoot, we can write code like this:

@Component
public class MyServletContextListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // TODO 在这里可以执行一些初始化操作,比如查询数据库,缓存数据,加载配置等
        System.out.println("Spring容器加载完成触发");
    }
}
           

Perform a callback after Spring has completed initialization:

Spring | Event listener applications and best practices

6.2 ContextClosedEvent

When the Spring container is shut down, the ContextClosedEvent event is fired. At this stage, all singleton beans have been destroyed.

@EventListener
public void handleContextClose(ContextClosedEvent event) {
    System.out.println("Context Closed: " + event.getTimestamp());
}
           

6.3 ContextStartedEvent

When a Spring context is started using the start() method of ConfigurableApplicationContext, the ContextStartedEvent event event is triggered.

@EventListener
public void handleContextStart(ContextStartedEvent event) {
    System.out.println("Context Started: " + event.getTimestamp());
}
           

6.4 ContextStoppedEvent

Correspondingly, when a Spring context is stopped using the stop() method of the ConfigurableApplicationContext, the ContextStoppedEvent event is triggered.

@EventListener
public void handleContextStop(ContextStoppedEvent event) {
    System.out.println("Context Stopped: " + event.getTimestamp());
}
           

6.5 ApplicationReadyEvent

The ApplicationReadyEvent event is triggered when the Spring application is running and ready to accept requests.

@EventListener
public void handleApplicationReady(ApplicationReadyEvent event) {
    System.out.println("Application Ready: " + event.getTimestamp());
}
           

6.6 Other Events

In addition to the above events, Spring also provides a series of other built-in events, such as RequestHandledEvent, ServletRequestHandledEvent, etc., which can help us understand and manage the running status of the application more comprehensively.

6.7 Summary

Understanding and leveraging Spring's built-in events can help us monitor the lifecycle and running status of applications more easily and quickly, and optimize application performance and stability. At the same time, it also provides us with a convenient means to listen to these events and execute custom logic to meet different business needs.

Analysis of advantages and disadvantages

Next, we will explore the advantages and disadvantages of Spring listeners in detail. Understanding these pros and cons will help us make more informed decisions about when and how to use Spring listeners.

7.1 Advantages

  • Low coupling: Spring listeners allow different components to interact with each other without the need for them to directly reference each other, helping to achieve low coupling and high cohesion of the code.
  • Easy to extend: Listeners make it easy to extend the system and add new features or behaviors to the system without having to modify existing code.
  • Powerful event processing capabilities: The event processing mechanism provided by Spring is powerful and flexible, which can cope with various complex business scenarios and meet diverse business needs.
  • Improved modularity: Listeners can clearly separate concerns, helping to organize code for different functions into different modules, improving code maintainability and readability.

7.2 Cons

  • Performance overhead: The use of listeners comes with a performance overhead, especially when a large number of events are triggered and processed, which can become a bottleneck for system performance.
  • Complexity: When there are a large number of listeners and events in a system, the complexity of managing and maintaining them increases, leading to errors and problems that are difficult to debug.
  • Not suitable for all scenarios: Listeners are not suitable for all scenarios. In some simple situations where a quick response is required, introducing a listener can seem too heavy and cumbersome.
Suggestion: When considering the use of Spring listeners, you should weigh the convenience and possible drawbacks that come with it. In complex business scenarios where events are really needed to decouple modules, Spring listeners are a very suitable choice. However, in simple scenarios where decoupling is not required, you should consider avoiding the use of listeners to reduce unnecessary complexity and performance overhead.

Best Practices

In actual development, how to use Spring listeners more reasonably and efficiently is crucial. Here are some best practices for using Spring listeners to help you apply Spring listeners more wisely and flexibly.

  • Clarify the type of event: When defining an event, clearly and unambiguously indicate the type of event and the information it carries, so as to ensure that the event can accurately reflect the state change of the system. This also contributes to the readability and maintainability of the code.
  • Proper division of listener responsibilities: Each listener should have a clear and single responsibility. Avoid dealing with too much unrelated logic in one listener, which can make the listener complex and difficult to maintain.
  • Optimize event publishing: Avoid overpublishing events. For example, publishing an event in a loop, or publishing an event with a lot of unnecessary information, can cause performance issues. When publishing events, you need to precisely control the scope and content of events to avoid unnecessary performance overhead.
  • Use asynchronous listeners: Where appropriate, using asynchronous listeners can improve the responsiveness and throughput of your system. Asynchronous listeners can process events in separate threads, preventing blocking the main thread and improving the availability of the system.
  • java
  • Copy the code
  • @Async @EventListener public void handleAsyncEvent(MyEvent event) { // 处理事件 }
  • Design the event propagation mechanism: Design the event propagation mechanism reasonably according to business needs. Sometimes, events need to be propagated in a certain order, or stop propagating after a listener has processed them, and a well-designed propagation strategy for the event is required.
  • Effective management of listeners: All listeners in the system need to be effectively managed and maintained. Regularly review the listener's code to ensure that it conforms to design principles, and to update and optimize the listener to keep it running efficiently.
  • Focus on listening to the test: The business logic in the listener also needs to be fully tested. Write unit tests and integration tests for the different logic of the listener to ensure that the listener works correctly in a variety of situations.
  • Documentation and comments: Provide clear and complete documentation and comments for listeners and events, which helps team members understand the functions and usage of the code and improve the team's development efficiency.

summary

In this article, we delve into the principles of Spring listeners, how to use them, practical examples based on listener design patterns, Spring's built-in events, source code analysis, pros and cons, and best practices. Below we will make a brief review and summary.

9.1 Review

Through learning, we learned:

  • Spring Listener Principle: Spring Listener is implemented based on the observer design pattern, which allows us to add responses to specific events without modifying existing code.
  • How to use: We learned how to define, register, and use listeners and how to publish events.
  • Handwritten example: Through a real-world case, we understand how to implement event listening and handling based on a listener design pattern.
  • Spring Built-in Events: Spring provides a series of built-in events to help us better manage and monitor the lifecycle and running status of our applications.
  • Source code analysis: We went deep into the source code and explored the working mechanism and implementation details of the Spring listener.
  • Advantages and disadvantages: We analyzed the advantages and disadvantages of Spring listeners, understood in which scenarios it is appropriate to use listeners, and what problems need to be paid attention to.
  • Best Practices: We have learned a series of best practices to guide you on how to use Spring listeners more appropriately and efficiently.

9.2 Conclusion

We hope that this article will help you gain a deeper understanding of Spring listeners, how to use them, and best practices, so that you can develop high-quality software products more efficiently. At the same time, we also hope that you will continue to learn, practice and explore, and discover more possibilities and innovative ways to use Spring listeners. If you have any suggestions or questions about this article, please feel free to ask. Thanks for reading!

作者:Kfaino

Link: https://juejin.cn/post/7298636610609807411