12, Using listeners in Spring Boot

Preface: without any fancy preface, it is a basic and practical thing.

1. Introduction to listener

What is a web listener? Web listener is a special class in Servlet, which can help developers monitor specific events in the web, such as the creation and destruction of ServletContext, httpsession and ServletRequest; Creation, destruction and modification of variables. Processing can be added before and after some actions to realize monitoring.

2. Use of listener in spring boot

There are many usage scenarios for web listeners, such as listening to servlet context to initialize some data, listening to http session to obtain the number of people currently online, listening to servlet request object requested by client to obtain user access information, and so on. In this section, we will mainly learn about the use of listeners in Spring Boot through these three actual use scenarios.

2.1 listening for Servlet context objects

Listening servlet context object can be used to initialize data for caching. What do you mean? Let me cite a very common scenario. For example, when users click on the home page of a site, they generally show some information of the home page, which remains unchanged basically or most of the time, but these information comes from the database. If users need to obtain data from the database every time they click, it is acceptable to have a small number of users. If the number of users is very large, it is also a great expense to the database.

For this kind of home page data, if most of them are not updated often, we can cache them. Every time the user clicks, we take them directly from the cache, which can not only improve the access speed of the home page, but also reduce the pressure on the server. If you are more flexible, you can add a timer to update the home page cache regularly. It is similar to the change of ranking in the home page of CSDN personal blog.

Let's write an example for this function demoļ¼ŒIn practice,
Readers can fully apply this code to realize the relevant logic in their own projects.
First write one Serviceļ¼ŒSimulate querying data from the database:

[@Service](https://my.oschina.net/service)
public class UserService {
   /**
	 * Get user information
	 * [@return](https://my.oschina.net/u/556800)
	 */
	public User getUser() {
		// In practice, the corresponding information will be queried from the database according to the specific business scenario
		return new User(1L, "Rational thinking", "123456");
	}
}

Then write a listener, implement the applicationlistener < ContextRefreshedEvent > interface, override the onApplicationEvent method and pass the ContextRefreshedEvent object in. If we want to refresh our preloaded resources when loading or refreshing the application context, we can do this by listening to ContextRefreshedEvent.

/**
 * Use ApplicationListener to initialize some data to the listener in the application domain
 */
[@Component](https://my.oschina.net/u/3907912)
public class MyServletContextListener implements ApplicationListener<ContextRefreshedEvent> {

	[@Override](https://my.oschina.net/u/1162528)
	public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
		// Get the application context first
		ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
		// Get the corresponding service
		UserService userService = applicationContext.getBean(UserService.class);
		User user = userService.getUser();
		// Get the application domain object and put the found information into the application domain
		ServletContext application = applicationContext.getBean(ServletContext.class);
		application.setAttribute("user", user);
	}
}

As described in the note, first obtain the application context through the contextRefreshedEvent, and then obtain the UserService bean through the application context. In the project, you can obtain other beans according to the actual business scenario, then call your own business code to obtain the corresponding data, and finally store it in the application domain, In this way, we can directly obtain information from the application domain in the front-end, so as to reduce the pressure of data request. Next, write a Controller to get user information directly from the application domain to test.

[@RestController](https://my.oschina.net/u/4486326)
@RequestMapping("/listener")
public class TestController {

	@Resource
	private UserService userService;

	@GetMapping("/user")
	public User getUser(HttpServletRequest request) {
		ServletContext application = request.getServletContext();
		return (User) application.getAttribute("user");
	}

}

Start the project and enter in the browser http://localhost:8080/listener/user, just test it,

If the user information is returned normally, it indicates that the data has been cached successfully. However, this kind of application is cached in memory, which will consume memory. I will talk about redis in later courses, and I will introduce redis cache to you at that time.

2.2 listening to HTTP Session object

The listener is also commonly used to monitor the session object to obtain the number of online users. Now many developers have their own websites. Monitoring the session to obtain the current number of users is a very common use scenario. Let's introduce how to use it.

@Component
public class MyHttpSessionListener implements HttpSessionListener {

	private static final Logger logger = LoggerFactory.getLogger(MyHttpSessionListener.class);

	/**
	 * Record the number of online users
	 */
	public Integer count = 0;

	@Override
	public synchronized void sessionCreated(HttpSessionEvent httpSessionEvent) {
		logger.info("New users are online");
		count++;
		httpSessionEvent.getSession().getServletContext().setAttribute("count", count);
	}

	@Override
	public synchronized void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
		logger.info("The user is offline");
		count--;
		httpSessionEvent.getSession().getServletContext().setAttribute("count", count);
	}
}

It can be seen that firstly, the listener needs to implement the httpsessionlister interface, then rewrite the sessionCreated and sessionDestroyed methods, pass an HttpSessionEvent object in the sessionCreated method, and then add 1 to the number of users in the current session. The sessionDestroyed method is just the opposite and will not be repeated. Then we write a Controller to test it.

/**
	 * Get the current number of people online. There is a bug in this method
	 * @param request
	 * @return
	 */
	@GetMapping("/total")
	public String getTotalUser(HttpServletRequest request) {
		Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
		return "Number of people currently online:" + count;
	}

The Controller directly obtains the number of users in the current session, starts the server, enters localhost:8080/listener/total in the browser, and you can see that the returned result is 1. Then open a browser and request the same address, and you can see that the count is 2, which is no problem. However, if you close a browser and open it again, it should still be 2 in theory, but the actual test is 3. The reason is that the session destruction method is not executed (you can observe the log printing on the background console). When it is reopened, the server cannot find the user's original session, so a session is re created. How to solve this problem? We can transform the above Controller method:

@GetMapping("/total2")
	public String getTotalUser(HttpServletRequest request, HttpServletResponse response) {
		Cookie cookie;
		try {
			// Record the sessionId in the browser
			cookie = new Cookie("JSESSIONID", URLEncoder.encode(request.getSession().getId(), "utf-8"));
			cookie.setPath("/");
			//Set the validity of the cookie to 2 days and set it to be longer
			cookie.setMaxAge( 48 * 60 * 60);
			response.addCookie(cookie);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
		return "Number of people currently online:" + count;
	}

It can be seen that the processing logic is to make the server remember the original session, that is, record the original sessionId in the browser, and pass the sessionId to the server when you open it next time, so that the server will not be re created. Restart the server and test it again in the browser to avoid the above problems.

2.3 listen to the Servlet Request object requested by the client

Using the listener to obtain the user's access information is relatively simple. Just implement the ServletRequestListener interface, and then obtain some information through the request object. as follows

@Component
public class MyServletRequestListener implements ServletRequestListener {

	private static final Logger logger = LoggerFactory.getLogger(MyServletRequestListener.class);

	@Override
	public void requestInitialized(ServletRequestEvent servletRequestEvent) {
		HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
		logger.info("session id For:{}", request.getRequestedSessionId());
		logger.info("request url For:{}", request.getRequestURL());

		request.setAttribute("name", "Rational thinking");
	}

	@Override
	public void requestDestroyed(ServletRequestEvent servletRequestEvent) {

		logger.info("request end");
		HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
		logger.info("request Saved in domain name The value is:{}", request.getAttribute("name"));

	}

}

This is relatively simple and will not be repeated. Next, write a Controller to test it

@GetMapping("/request")
	public String getRequestInfo(HttpServletRequest request) {
		System.out.println("requestListener Initialized in name Data:" + request.getAttribute("name"));
		return "success";
	}

3. Custom event listening in spring boot

In actual projects, we often need to customize some events and listeners to meet business scenarios. For example, in microservices, there will be such A scenario: microservice A needs to notify microservice B to process another logic after processing A logic, or microservice A needs to synchronize data to microservice B after processing A logic. This scenario is very common, At this time, we can customize events and listeners to listen. Once we listen to an event in microservice A, we will notify microservice B to process the corresponding logic.

3.1 user defined events

Custom events need to inherit the ApplicationEvent object, define a User object in the event to simulate data, and transfer the User object in the construction method for initialization. As follows:

public class MyEvent extends ApplicationEvent {

	private User user;

	public MyEvent(Object source, User user) {
		super(source);
		this.user = user;
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}
}

3.2 custom listener

Next, customize a listener to listen to the MyEvent event event defined above. The custom listener needs to implement the applicationlister interface. As follows:

@Component
public class MyEventListener implements ApplicationListener<MyEvent> {
	@Override
	public void onApplicationEvent(MyEvent myEvent) {
		// Get the information in the event
		User user = myEvent.getUser();
		// Handle events. In the actual project, you can notify other modules or handle other logic, etc
		System.out.println("user name:" + user.getUsername());
		System.out.println("password:" + user.getPassword());

	}
}

Then rewrite the onApplicationEvent method to pass in the custom MyEvent event, because in this event, we define the User object (this object is actually the data to be processed, which will be simulated below), and then we can use the information of this object.

OK, after defining the event and listener, you need to publish the event manually so that the listener can listen. This needs to be triggered according to the actual business scenario. For the example of this article, I write a trigger logic, as follows:

@Service
public class UserService {

	@Resource
	private ApplicationContext applicationContext;

	/**
	 * Publish event
	 *
	 * @return
	 */
	public User getUser2() {
		User user = new User(1L, "Rational thinking", "123456");
		// Publish event
		MyEvent event = new MyEvent(this, user);
		applicationContext.publishEvent(event);
		return user;
	}

}

Inject ApplicationContext into the service. After the business code is processed, manually publish the MyEvent event event through the ApplicationContext object, so that our customized listener can listen and process the business logic written in the listener

Finally, write an interface in the Controller to test:
@GetMapping("/request")
	public String getRequestInfo(HttpServletRequest request) {
		System.out.println("requestListener Initialized in name Data:" + request.getAttribute("name"));
		return "success";
	}

Enter in the browser http://localhost:8080/listener/publish And then look at the controls The user name and password printed on the console indicates that the custom listener has taken effect.

4. Summary

This lesson systematically introduces the principle of listener and how to use listener in Spring Boot, and lists three common cases of listener, which has good practical significance. Finally, it explains how to customize events and listeners in the project, and gives specific code models combined with common scenarios in microservices, which can be applied to practical projects. I hope readers can digest them carefully.

Have a nice weekend!

Keywords: JavaEE Redis Spring Boot

Added by azul85 on Fri, 04 Mar 2022 22:13:33 +0200