我们先说说它的特点,优缺点,以及使用场景,然后再说具体是怎么做的
Spring事件驱动的优点
-
松耦合
事件驱动模型通过发布-订阅机制促进组件间的解耦,发送者和接收者不需要直接知道对方的存在,只需关注事件本身,提高了系统的灵活性和可扩展性。 -
可扩展性
添加新的事件或监听器变得容易,只需实现相应的接口或继承类即可,不影响现有代码,有助于应对需求变化和系统升级。 -
异步处理
事件可以被异步处理,使得应用可以继续执行而不必等待事件处理完成,提升了响应速度和吞吐量。 -
模块化
支持模块化开发,不同的模块可以独立定义自己的事件和监听器,便于维护和复用。 -
简化复杂交互
对于多组件交互或复杂的业务流程,事件驱动模型提供了一种清晰的通信机制,降低了逻辑复杂度。
Spring事件驱动的缺点
-
调试困难
由于事件的异步和解耦特性,追踪事件的发布、传递和处理流程可能会比较困难,尤其是在分布式系统中。 -
性能考量
大量的事件监听器或频繁的事件发布可能会消耗较多资源,增加系统负担,影响性能。 -
过度解耦的风险
过度依赖事件驱动可能导致系统架构过于松散,难以理解各部分如何协作,增加了维护难度。 -
不能用在分布式上面(一般分布式我们都用MQ)
应用场景: -
异步处理
如邮件发送、消息队列推送、日志记录等操作,可以异步执行不阻塞主线程。 -
用户行为跟踪
记录用户操作,如点击、登录、购买等,触发后续处理如统计分析、推荐系统更新等等。 -
工作流引擎
在工作流系统中,状态变更、任务完成等事件可以触发下一步动作,实现流程自动化。 -
定时任务
结合Spring的定时任务调度,事件可以按计划触发,执行定时处理逻辑。所以Spring事件驱动机制适用于需要解耦、异步处理或灵活扩展的场景,但也得注意潜在的管理和性能挑战,不能过度使用。
下面我们搞一搞Spring的事件
当我们去看Spring源码的时候自然而然会看到事件,Spring自身定义了好几种事件有不同的用途,咱们介绍下
Spring主要的内置事件驱动(Spring事件驱动)
ContextRefreshedEvent 上下文更新
ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext接口中使用 refresh() 方法来发生。此处的初始化是指:所有的Bean被成功装载后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用。
ContextStartedEvent 上下文开始事件
当使用 ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以在接受到这个事件后重启任何停止的应用程序。
ContextStoppedEvent 上下文停止事件
当使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时发布这个事件。你可以在接受到这个事件后做必要的清理的工作
ContextClosedEvent 上下文关闭事件
当使用 ConfigurableApplicationContext接口中的 close()方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端,它不能被刷新或重启
RequestHandledEvent 请求处理事件
这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件
除了上面介绍的事件以外,还可以通过扩展 ApplicationEvent 类来开发自定义的事件。
自定义事件(事件驱动)
我们可以定义一个事件类继承ApplicationEvent ,并设置为泛型,它毕竟是个类,我们可以在里面写点字段啊方法的都行,无非发布后在监听的地方去执行,那么具体你想搞些什么发布出去让 监听去做的,或者只是通知一声,都是你说了算
/*** 自定义事件 为什么自定义事件而不用ApplicationEvent?因为它不带泛型,传值只用Object不友好*/
public class CustomApplicationEvent<T> extends ApplicationEvent {//我们在事件中可以定义字段private String fieldTest;public CustomApplicationEvent(T source) {super(source);Thread thread = Thread.currentThread();System.out.println("Created a Custom event " +thread.getName());}public String getFieldTest(){return fieldTest;}public void setFieldTest(String fieldTest){this.fieldTest = fieldTest;}
}
自定义监听器都要继承ApplicationListener< E extends ApplicationEvent >,大家可以看到这个类的泛型有一个要求,就是必须要继承ApplicationEvent 的扩展事件类,这也就是为什么上面我们定义事件类的时候继承ApplicationEvent的原因
那么监听器实现有两种方式,并且支持同步异步,废话少说,上酸菜
- 使用@EventListener注解式,要监听哪个事件你就放到入参中
- 实现ApplicationListener<事件> 这个事件就是你自定义那个事件
/*** 监听器*/
@Component
public class CusEventListener {/*** 写法一*/@EventListenerpublic void handlerEvent(CustomApplicationEvent event) {System.out.println("listener handlerEvent……" + event.getSource());System.out.println("listener handlerEvent field……" + event.getFieldTest());}@Async@EventListenerpublic void handlerEvent2(CustomApplicationEvent event) {System.out.println("listener handlerEvent2……" + event.getSource());ThreadUtil.sleep(5000);System.out.println("listener handlerEvent2 field……" + event.getFieldTest());}/*** 写法二* 监听器1*/@Componentclass CustomEventListener implements ApplicationListener<CustomApplicationEvent> {@Overridepublic void onApplicationEvent(CustomApplicationEvent event) {Object source = event.getSource();//handle eventThread thread = Thread.currentThread();System.out.println("listener……" + thread.getName());}}/*** 监听器2*/@Componentclass CustomEvent2Listener implements ApplicationListener<CustomApplicationEvent> {@Overridepublic void onApplicationEvent(CustomApplicationEvent event) {Object source = event.getSource();//handle eventThread thread = Thread.currentThread();System.out.println("listener2……" + thread.getName());}}
}
@Autowired
private ApplicationContext applicationContext;@Override
public void publish(){Animal animal = new Animal();animal.setName("dog");CustomApplicationEvent event = new CustomApplicationEvent(animal);event.setFieldTest("我是事件中的字段");applicationContext.publishEvent(event);System.out.println("publish end");
}
你注意到没有,上面有个@Async就是异步的作用,但是怎么让它异步生效,还需要有个配置
配置类@Configuration @EnableAsync使异步生效,并且定义好线程池,异步就做到了
@Configuration
@EnableAsync
public class ExecutorPool {private static final Log log = LogFactory.get();private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 2;@Bean("asyncExecutor")public ThreadPoolTaskExecutor asyncExecutor() {log.info("start asyncServiceExecutor : {}, {}", CORE_POOL_SIZE, MAX_POOL_SIZE);ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//配置核心线程数executor.setCorePoolSize(CORE_POOL_SIZE);//配置最大线程数executor.setMaxPoolSize(MAX_POOL_SIZE);//配置队列大小executor.setQueueCapacity(2000);//配置线程池中的线程的名称前缀executor.setThreadNamePrefix("async-service-");// rejection-policy:当pool已经达到max size的时候,如何处理新任务// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//执行初始化executor.initialize();return executor;}
}
好了,此间茶话会到此结束,大家可以执行看看打印情况,有疑问请留言,翠花,捡桌子!