Spring Boot | 事件监听器异步处理事件,实现代码解耦

时间:2021-07-18 00:42:55

一、简介

Spring Boot事件监听器(Event Listener)用于在应用程序的生命周期中,监听Spring Boot应用程序中各种事件的发生,以便在事件发生时执行某些特定的操作。

二、集成步骤

1、创建自定义事件类

package cn.ddcherry.springboot.demo.event;

import lombok.Getter;
import org.springframework.context.ApplicationEvent;

@Getter
public class CustomEvent extends ApplicationEvent {

	private String message;

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

这里笔者汪小成创建了一个简单的事件类,继承了org.springframework.context.ApplicationEvent,事件类只有一个 String 类型的消息属性。

事件类不是必须继承ApplicationEvent,一个普通的类也可以作为一个事件类。

2、创建事件监听器

创建一个监听器类并实现ApplicationListener接口,该接口有一个onApplicationEvent()方法,用于处理事件:

package cn.ddcherry.springboot.demo.listener;

import cn.ddcherry.springboot.demo.event.CustomEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
	@Override
	public void onApplicationEvent(CustomEvent event) {
		log.info("事件监听器 - 收到消息:{}", event.getMessage());
	}
}

在上面的代码中,CustomEventListener实现了ApplicationListener<CustomEvent>接口,并且实现了onApplicationEvent方法。当一个CustomEvent事件被发布时,onApplicationEvent方法将被自动调用。

CustomEventListener上使用@Component注解,将其作为 Spring 组件进行注册。

创建事件监听器有两种方式:

(1) 实现ApplicationListener接口;

(2) 使用@EventListener注解,@EventListener注解可以直接在方法上使用,以指定该方法为事件监听器。

@Component
public class CustomEventListener {
    @EventListener
    public void handleMyEvent(CustomEvent event) {
        //处理事件
    }
}

3、创建事件发布者

package cn.ddcherry.springboot.demo.publisher;

import cn.ddcherry.springboot.demo.event.CustomEvent;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@AllArgsConstructor
public class CustomEventPublisher {

	private ApplicationEventPublisher publisher;

	public void publish(String message) {
		CustomEvent customEvent = new CustomEvent(this, message);
		publisher.publishEvent(customEvent);
		log.info("事件发布成功 - 消息:{}", message);
	}
}

上面的示例中,通过构造方法注入的方式自动注入ApplicationEventPublisher,然后在方法内部调用了ApplicationEventPublisherpublishEvent(...)方法发布了一个自定义事件CustomEvent

4、创建测试控制器类

为了演示事件监听,我们创建了一个控制器。代码如下:

package cn.ddcherry.springboot.demo.controller;

import cn.ddcherry.springboot.demo.publisher.CustomEventPublisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/event")
public class EventController {

	@Resource
	private CustomEventPublisher customEventPublisher;

	@GetMapping("/publish")
	public Map<String, Object> publish(@RequestParam("message") String message) {
		customEventPublisher.publish(message);

		Map<String, Object> resultMap = new HashMap<>(16);
		resultMap.put("data", "事件发布成功");
		resultMap.put("message", message);
		return resultMap;
	}
}

5、测试

启动spring Boot项目,然后在浏览器地址栏输入http://localhost:8080/event/publish?message=嗨皮汪小成,在控制台我们可以看到如下输出内容:

事件监听器 - 收到消息:嗨皮汪小成
事件发布成功 - 消息:嗨皮汪小成

异步处理事件

默认情况下,Spring Boot 事件监听器会同步处理事件。也就是说,当事件触发时,Spring Boot 事件监听器会在同一个线程中处理该事件。这种处理方式的好处是可以保证处理事件的顺序和一致性,但如果事件处理比较耗时,可能会阻塞主线程。

Spring Boot 提供了异步处理事件的方式。可以通过在事件监听器上加上 @Async 注解来实现异步处理事件。同时还要在项目启动类上添加@EnableAsync注解来开启异步执行器的支持。

1、创建异步事件监听器

package cn.ddcherry.springboot.demo.listener;

import cn.ddcherry.springboot.demo.event.CustomEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Slf4j
@Async
@Component
public class CustomAsyncEventListener implements ApplicationListener<CustomEvent> {
	@Override
	public void onApplicationEvent(CustomEvent event) {
		log.info("事件监听器(异步) - 收到消息:{},开始处理。", event.getMessage());
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		log.info("事件监听器(异步) - 处理完成!");
	}
}

从上面的代码可以看出,笔者汪小成在事件监听器上加上 @Async 注解来实现异步处理事件,使用Thread.sleep(2000)模拟监听器处理事件耗时。

2、在项目启动类上添加@EnableAsync注解

package cn.ddcherry.springboot.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

+ @EnableAsync
@SpringBootApplication
public class EventApplication {
	public static void main(String[] args) {
		SpringApplication.run(EventApplication.class, args);
	}
}

3、测试

启动spring Boot项目,然后在浏览器地址栏输入http://localhost:8080/event/publish?message=嗨皮汪小成,在控制台我们可以看到如下输出内容:

事件监听器(异步) - 收到消息:嗨皮汪小成,开始处理。
事件发布成功 - 消息:嗨皮汪小成
事件监听器(异步) - 处理完成!

总结

使用事件的好处是解耦,可以方便地实现模块间的通信,同时也可以提高代码的可读性和可维护性。

项目源码地址:https://github.com/wanggch/spring-boot-demos/tree/main/05.spring-boot-event

Spring Boot系列文章