事件机制是Spring的一个功能,目前我们使用了SpringBoot框架,所以记录下事件机制在SpringBoot框架下的使用,同时实现异步处理。事件机制其实就是使用了观察者模式(发布-订阅模式)。
Spring的事件机制经过如下流程:
- 1、自定义事件,继承org.springframework.context.ApplicationEvent抽象类
- 2、定义事件监听器,实现org.springframework.context.ApplicationListener接口
- 3、在Spring容器中发布事件
SpringBoot的实例程序
实现一个保存用户的时候,向用户提供的邮箱发送一封邮件的功能,同时采用异步处理。
自定义事件
import org.springframework.context.ApplicationEvent;public class EmailEvent extends ApplicationEvent {private static final long serialVersionUID = 3733891603598996786L;private String emailAddress;public EmailEvent(String emailAddress) {super(emailAddress);this.emailAddress = emailAddress;}public String getEmailAddress() {return emailAddress;}public void setEmailAddress(String emailAddress) {this.emailAddress = emailAddress;}
}
定义事件监听器
import java.util.concurrent.TimeUnit;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;@Component
public class EmailEventListener implements ApplicationListener<EmailEvent> {private static Logger log = LoggerFactory.getLogger(EmailEventListener.class);// 异步处理@Async@Overridepublic void onApplicationEvent(EmailEvent event) {log.info("监听到事件--邮箱地址:" + event.getEmailAddress());//模拟处理的耗时3stry {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}log.info("事件处理完成");}}
发布事件
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;@Component
public class EmailEventPublish {@Autowiredprivate ApplicationContext applicationContext;public void publishEvent(String emailAddress) {EmailEvent event = new EmailEvent(emailAddress);applicationContext.publishEvent(event);}}
调用事件
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import com.example.demo.event.EmailEventPublish;@RestController
public class EventController {private static Logger log = LoggerFactory.getLogger(EventController.class);@Autowiredprivate EmailEventPublish emailEventPublish;@RequestMapping("/event")public void publishEvent(@RequestParam String emailAddress) {// 发布事件 -- 采用异步处理emailEventPublish.publishEvent(emailAddress);// 正常该语句先执行log.info("Controller业务处理");}
}
结果
访问如下地址
http://localhost:8080/event?emailAddress=plf@163.com
结果为
2023-08-04 21:21:14.338 INFO 6400 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2023-08-04 21:21:14.338 INFO 6400 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2023-08-04 21:21:14.370 INFO 6400 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 32 ms
2023-08-04 21:21:14.429 INFO 6400 --- [nio-8080-exec-1] .s.a.AnnotationAsyncExecutionInterceptor : No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either
2023-08-04 21:21:14.534 INFO 6400 --- [nio-8080-exec-1] c.e.demo.controller.EventController : Controller业务处理
2023-08-04 21:21:14.535 INFO 6400 --- [cTaskExecutor-1] c.example.demo.event.EmailEventListener : 监听到事件--邮箱地址:plf@163.com
2023-08-04 21:21:17.536 INFO 6400 --- [cTaskExecutor-1] c.example.demo.event.EmailEventListener : 事件处理完成
上述结果可知是实现了异步处理,先打印了事件之后的程序,等时间到再执行监听程序的代码。
实现异步处理就是在监听事件执行业务代码的方法上添加@Async
注解,同时在启动类上添加@EnableAsync
即可。
上面的日志还提到了TaskExecutor
,这是如果有自定义的线程池就会去调用,如果没有就用默认的。我们也可以自己定义一个TaskExecutor
。
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@EnableAsync
@Configuration
public class ThreadPool implements AsyncConfigurer {@Nullable@Override@Bean("taskExecutor")public Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 线程池创建时候初始化的线程数executor.setCorePoolSize(10);// 线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程executor.setMaxPoolSize(20);// 用来缓冲执行任务的队列executor.setQueueCapacity(200);// 允许线程的空闲时间60秒executor.setKeepAliveSeconds(60);// 线程池名的前缀executor.setThreadNamePrefix("taskExecutor-");// 线程池对拒绝任务的处理策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}@Nullable@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return null;}
}
结果
2023-08-04 21:27:36.507 INFO 7848 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2023-08-04 21:27:36.507 INFO 7848 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2023-08-04 21:27:36.537 INFO 7848 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 30 ms
2023-08-04 21:27:36.757 INFO 7848 --- [nio-8080-exec-2] c.e.demo.controller.EventController : Controller业务处理
2023-08-04 21:27:36.757 INFO 7848 --- [ taskExecutor-1] c.example.demo.event.EmailEventListener : 监听到事件--邮箱地址:plf@163.com
2023-08-04 21:27:39.757 INFO 7848 --- [ taskExecutor-1] c.example.demo.event.EmailEventListener : 事件处理完成
可知是使用我们定义的线程池[ taskExecutor-1]
。
总结
Spring的事件机制是一个很实用的一个功能,在监听和异步处理相关的功能比较适合。