Spring Event事件通知
本文共 6,738 字,预计阅读时间 22 分钟
Spring的事件通知机制是一项很有用的功能,使用事件机制可将相互耦合的代码解耦,从而方便功能的开发。
1.入门案例
1.1环境准备
新建一个SpringBoot的项目,导入web的依赖,编写一个controller接口:
package com.zys.springboottestexample.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @RestController @RequestMapping("/user") public class UserController { @PostMapping("/save") public void save(Map map) { System.out.println(map); } }
1.2使用Spring Event
1)使用说明
使用用事件需要以下的几个步骤:
第一:定义事件,继承ApplicationEvent
第二:定义监听,实现ApplicationListener接口或添加注解@EventListener
第三:发布事件,调用ApplicationEventPublisher.publishEvent()或ApplicationContext.publishEvent()
2)定义事件
package com.zys.springboottestexample.event; import org.springframework.context.ApplicationEvent; // 定义一个事件 public class EventDemo extends ApplicationEvent { private String message; public EventDemo(Object source, String message) { super(source); this.message = message; } public String getMessage() { return message; } }
3)定义监听
package com.zys.springboottestexample.listener; import com.zys.springboottestexample.entity.EventDemo; import org.springframework.context.ApplicationListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; // 定义一个事件监听者 @Component public class EventDemoListener implements ApplicationListener<EventDemo> { @Override public void onApplicationEvent(EventDemo event) { System.out.println("当前线程:" + Thread.currentThread().getId()); System.out.println("receiver " + event.getMessage()); } }
4)发布事件
package com.zys.springboottestexample.service; import com.zys.springboottestexample.entity.EventDemo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; // 事件发布的方法 @Component public class EventPublishService { @Autowired private ApplicationEventPublisher applicationEventPublisher; public void publish(String message) { EventDemo demo = new EventDemo(this, message); applicationEventPublisher.publishEvent(demo); } }
5)触发事件
当事件发布后,在需要的地方就可以触发事件了。在上述UserController中触发:
@Autowired private EventPublishService eventPublishService; @PostMapping("/save") public void save(Map map) { System.out.println(map); System.out.println("当前线程:" + Thread.currentThread().getId()); eventPublishService.publish("添加成功"); }
6)测试。访问localhost:8080/user/save,在控制台打印了信息
7)异步处理
上述看到打印结果分析,处理接口的线程和事件监听使用的是同一个线程。一般会使用异步的方式。
第一步:首先在事件监听的方法上添加异步的注解@Async
第二步:然后在启动类上启用异步@EnableAsync
第三步:重启项目,再次访问上述接口,控制台打印的线程id就不一样了
2.实际应用
2.1应用场景
当然在实际应用中,Spring Event因解耦的特性也显得格外重要。比如现有一个请假申请的方法,在申请保存到数据库的同时需要给上级领导发送系统和邮件通知。当然可以在保存申请的代码后面假设这些操作,但是这样的代码违反了设计模式的多项原则:单一职责原则、迪米特法则、开闭原则。也就是说,比如将来评论添加成功之后还需要有发送短信通知,这时又要去修改存申请代码才能符合需求。若使用了事件通知机制,则无需修改原有功能,只需在发布通知功能中调用短信发送功能即可。
2.2自定义日志收集的starter
本章节通过自定义一个starter,名为log-spring-boot-starter,用来拦截用户请求并收集操作日志信息,收集的信息会通过监听器返回给使用者,使用者再获取。
源码:https://github.com/zhongyushi-git/zxh-starter-collection.git
2.2.1开发步骤
具体步骤如下:
1)定义日志对象LogDTO
2)定义日志操作事件类LogEvent
3)定义@Log注解
4)定义切面类LogAspect
5)在切面类LogAspect中定义切点,拦截Controller中添加@Log注解的方法
6)在切面类LogAspect中定义前置通知,在前置通知方法中收集操作日志相关信息封装为LogDTO对象并保存到ThreadLocal中
7)在切面类LogAspect中定义后置通知,在后置通知方法中通过ThreadLocal获取LogDTO并继续设置其他的操作信息到LogDTO
8)在切面类LogAspect的后置通知方法中发布事件LogEvent
9)定义监听器LogListener,监听日志发布事件LogEvent
10)定义配置类LogAutoConfiguration,用于自动配置切面LogAspect对象。在配置类有一个属性sys.log.enabled
,表示是否启用日志收集,值true启用,值false不启用,若不配置时也生效。
11)定义starter所需的META-INF/spring.factories文件,配置自动配置类
2.2.2说明
1)使用@Log注解时,参数value是接口的描述,type是日志的类型,1表示操作日志,2表示登录日志。
2)代码中的自动配置类的关键注解说明:
注解名称 | 描述 |
@ConditionalOnWebApplication |
只有当前项目是Web项目的条件下生效 |
@ConditionalOnProperty |
指定的属性是否有指定的值,通过havingValue与配置文件中的值对比,返回为true则配置类生效,反之失效 |
@ConditionalOnMissingBean |
用来修饰bean,当注册此bean时,会检查是否已经注册过此Bean,若注册过就不会再次注册,若没有注册过则进行注册,保证此bean只有一个。 |
2.3使用日志收集的starter
1)新建SpringBoot的项目,导入web和此starter依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.zxh.boot</groupId>
<artifactId>log-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2)定义LogService类,用于保存日志信息
package com.zys.springboottestexample.service; import com.zxh.boot.log.entity.LogDTO; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @Slf4j public class LogService { public void saveLog(LogDTO logDTO){ log.info(logDTO.toString()); //保存到数据库 } }
这里只是打印,实际中将对象按需求存入数据库即可
3)定义配置类LogConfiguration,用于初始化监听
package com.zys.springboottestexample.config; import com.zxh.boot.log.listener.LogListener; import com.zys.springboottestexample.service.LogService; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 日志配置类 */ @Configuration public class LogConfiguration { /** * 初始化监听器, * @param logService * @return */ @Bean @ConditionalOnMissingBean public LogListener logListener(LogService logService){ //函数式接口 return new LogListener(logDTO -> logService.saveLog(logDTO)); } }
当日志事件发布后,会在Log监听器中进行监听,并调用consumer.accept()方法,而在初始化监听器中函数式接口又指明了调用的方法,则最终会自动调用logService.saveLog()方法。
4)创建UserController类,定义一个接口,添加日志注解
package com.zys.springboottestexample.controller; import com.zxh.boot.log.annotation.Log; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @GetMapping("/save") @Log("用户添加") public String save(String name) { int t=10/0; return "你好啊"; } }
5)启动项目,访问localhost:8080/user/save?name=123即可在控制台看到打印的日志对象
从控制台可以看出,虽然在代码中故意制造了异常,但仍然有日志信息,那么这些日志被记录到数据库,就可查询错误的信息,这就体现了日志的重要性。
6)若不再使用此starter,除了删除其依赖外,还可以直接在配置文件中配置:
sys.log.enabled=false
那么即使加了@Log注解,也不会生效。
7)获取登录用户信息
可以通过设置请求头header让日志获取登录用户信息,需要设置的有两个,分别是userId和userName,例如:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!