用来做什么?
当某个业务完成后,需要做另外一些操作。如果写在代码中,一个一个去请求,虽然也能完成,但是这样代码耦合,遇见新增操作也需要找到代码进行修改。
有一种更好的方式,那就是事件监听,事件监听也是设计模式中 发布-订阅模式、观察者模式的一种实现。
可以将业务做完后,发布一个事件,将必须的参数通过事件一同发布出去。发布后所有订阅该事件的监听都会被触发并拿到传递的参数,可以在每个监听中分别进行不同的操作。比如减库存、清购物车等等。
1.定义一个事件,用来发布、提供给监听器去监听。
事件可以简单理解为,对中间参数的传递介质来使用。业务完成后,将各个监听器需要的参数通过有参构造方法进行依赖注入,将DTO传入事件内部,监听器监听到后再从接口中取到DTO实体,再进而取出来参数,完成参数传递。(事件中定义getter方法来供给外部获取DTO)。
/**
* 事件
*
* @author byChen
* @date 2022/6/15
*/
public class MyUserEvent extends ApplicationEvent {
/**
* private私有化封装
*/
private UserInfo userInfo;
/**
* 事件中也可以定义方法
* 事件中应该定义一个getter方法,这样才能有方法将接口参数获取出来,因为接口参数是private封装起来的
*
* @return
*/
public UserInfo getUserInfo() {
return this.userInfo;
}
/**
* 有参构造接口,用来传参
* 事件接口中还可以对这些参数进行统一修改、或者进行一些切面操作。
*
* @param source
*/
public MyUserEvent(UserInfo source) {
//必要代码行
super(source);
//依赖注入
this.userInfo = source;
//参数统一处理
userInfo.setNickName("事件中被创建");
System.out.println("===MyUserEvent被发布==");
//切面逻辑
System.out.println("====切面逻辑===");
}
}
2.创建监听器来监听事件
创建监听器有两种方法
① 实现ApplicationListener 接口
Object是需要监听的事件的实体,需要重写 onApplicationEvent 方法,监听到事件后就会触发 onApplicationEvent 接口
/**
* 创建事件监听类,需要实现ApplicationListener接口,指定事件类
* 当事件被发布时,会触发onApplicationEvent接口,把事件传入接口
*
* @author byChen
* @date 2022/6/15
*/
@Component
public class MyListenerEventTwo implements ApplicationListener<MyUserEvent> {
@Autowired
UserInfoService userInfoService;
/**
* 监听事件
*
* @param event
*/
@Override
public void onApplicationEvent(MyUserEvent event) {
UserInfo userInfo = event.getUserInfo();
userInfo.setId("2");
userInfo.setPhone("222222");
userInfo.setUserName("被监听事件二创建");
userInfoService.save(userInfo);
}
}
这种方法不经常使用,一般使用第二种方法
② 使用注解 @EventListener(classes = {Object.class})
使用注解更简便,并且同一Java文件内可以有多个监听事件。对于同一个事件的不同监听,可以使用 **@Order()**注解来指定执行顺序。
/**
* 使用注解 @EventListener(classes = {MyUserEvent.class})
* 被该注解修饰的方法,即监听事件的方法。
*
* @author byChen
* @date 2022/6/15
*/
@Component
public class MyListenerEventTwo {
@Autowired
UserInfoService userInfoService;
/**
* 监听事件一
*
* @param event
*/
@EventListener(classes = {MyUserEvent.class})
@Order(1)
public void onApplicationEventOne(MyUserEvent event) {
UserInfo userInfo = event.getUserInfo();
userInfo.setId("1");
userInfo.setPhone("11111");
userInfo.setUserName("被监听事件一创建");
userInfoService.save(userInfo);
}
/**
* 监听事件二
*
* @param event
*/
@EventListener(classes = {MyUserEvent.class})
@Order(2)
public void onApplicationEventTwo(MyUserEvent event) {
UserInfo userInfo = event.getUserInfo();
userInfo.setId("2");
userInfo.setPhone("222222");
userInfo.setUserName("被监听事件二创建");
userInfoService.save(userInfo);
}
}
3.事件发布(事件广播),监听器触发
事件需要被spring容器发布后,才会被事件给监听到并执行监听器内部逻辑。
现在需要发布的位置注入依赖
@Autowired
private ApplicationContext applicationContext;
然后将事件交由依赖来发布
@GetMapping("/test12")
@Transactional(rollbackFor = Exception.class)
public void test() {
System.out.println("发布事件");
//通过ApplicationContext 发布事件
//事件因为有有参构造,因此可以注入参数
applicationContext.publishEvent(new MyUserEvent(new UserInfo()));
}
4.监听器监听到,触发执行接口
注意: 这里面的@Order()注解是spring里面的注解,不要引入错误
事务相关
到这里,事件发布、监听触发就完成了。但是当前发布事件的模块与各个监听模块都位于同一个事务中,因此主发布模块或者监听模块发生异常报错,都是会同步回滚的。
在监听方法上面加上 @Async 异步注解,即可脱离事务管理(启动类需要加上@EnableAsync注解才可以生效异步)。加之前还是相当于同一个线程,不管主业务还是监听,只要一方发生报错,都会回滚。加之后就相当于事务分离开了。
因为指定执行顺序是在一个线程中指定的,因此order注解与Async注解不能同时使用
@Component
public class MyListenerEventTwo {
@Autowired
UserInfoService userInfoService;
/**
* 监听事件
* 监听事件默认跟发布事件的进程处于同一个事务,
* 1.加上@Async异步注解,即可脱离事务管理(启动类需要加上@EnableAsync注解才可以生效异步),
* 加之前还是相当于同一个线程,不管主业务还是监听,只要一方发生报错,都会回滚
* ps: @EventListener使用该注解可以指定多个事件实体去监听(缺点是入参参数是固定不能改变的)
* 使用order注解可以指定针对同一事件的监听方法的触发顺序(order注解与Async注解不能同时使用)
*
* @param event
*/
@EventListener(classes = {MyUserEvent.class})
@Async
public void onApplicationEventOne(MyUserEvent event) {
System.out.println("监听器一启动");
UserInfo userInfo = event.getUserInfo();
userInfo.setId("1");
userInfo.setPhone("11111");
userInfo.setUserName("被监听事件一创建");
userInfoService.save(userInfo);
}
/**
* 监听事件二
*
* @param event
*/
@EventListener(classes = {MyUserEvent.class})
@Async
public void onApplicationEventTwo(MyUserEvent event) {
System.out.println("监听器二启动");
UserInfo userInfo = event.getUserInfo();
userInfo.setId("2");
userInfo.setPhone("222222");
userInfo.setUserName("被监听事件二创建");
userInfoService.save(userInfo);
}
}
完结