一.概述
监听的用途很多,根据业务需要可以选择适当的监听来完成想要处理的功能,这里使用监听来完成业务的解耦。
用户注册流程:
①数据入库 -> ②发送激活用户邮件 -> ③赠送初始积分
当用户注册时,主流程其实只需完成①数据入库,然后返回注册成功信息给用户,主流程其实就可以结束了,如果②和③也跟①写到一起,那么注册耗时将变长,用户体验变差,这时需要进行业务的拆分,主流程只需要完成①,子流程②和③拆分出来。使用监听器,当应用监听到用户注册事件,单独去完成②和③,从而达到不阻塞主流程的目的。
截图是伪代码,需要你脑补一下实际代码。。。😄
二.创建事件
监听是围绕着事件进行处理的,所以需要先创建用户注册的事件,继承ApplicationEvent,并重载方法
public ApplicationEvent(Object source) {
super(source);
}
public class RegistUserEvent extends ApplicationEvent {
private User user;
public RegistUserEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() {
return user;
}
}
三.创建监听(注解形式)
使用注解来注册监听事件@EventListener,类的注解@Component不能忽略,否则事件将不会被监听到
/**
* 注解形式的监听
*/
@Component
public class AnnotationRegistUserListen {
Logger logger = LoggerFactory.getLogger(AnnotationRegistUserListen.class);
// @Async
@EventListener
public void registUser(RegistUserEvent registUserEvent) {
try {
Thread.sleep(5000L);
} catch (Exception e) {
e.printStackTrace();
}
User user = registUserEvent.getUser();
logger.info("注解形式的监听{}", JSONObject.toJSONString(user));
}
}
四.创建监听(接口形式)
实现接口ApplicationListener并指定泛型为RegistUserEvent,
重载方法onApplicationEvent,当调用监听时方法将会被执行。
/**
* 实现接口形式的监听
*/
@Component
public class InterfaceRegistUserListen implements ApplicationListener<RegistUserEvent> {
Logger logger = LoggerFactory.getLogger(InterfaceRegistUserListen.class);
//@Async
@Override
public void onApplicationEvent(RegistUserEvent registUserEvent) {
try {
Thread.sleep(5000);
User user = registUserEvent.getUser();
logger.info("实现接口形式的监听{}", JSONObject.toJSONString(user));
} catch (Exception e) {
e.printStackTrace();
}
}
}
五.创建监听(可以指定调用顺序的方式)
前面两种方式创建的监听是无序的,通过这种方式创建的监听,使用getOrder可以指定执行的顺序,值越小将优先调用
ps:
只有在方法为同步状态下时,排序才有效
/**
* 同步状态下,可以指定顺序的监听实现
*/
@Component
public class SendMailListen implements SmartApplicationListener {
Logger logger = LoggerFactory.getLogger(SendMailListen.class);
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return RegistUserService.class == sourceType;
}
@Override
public int getOrder() {
return 100;
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
return RegistUserEvent.class == aClass;
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
RegistUserEvent registUserEvent = (RegistUserEvent)applicationEvent;
logger.info("发送邮件的监听{}", JSONObject.toJSONString(registUserEvent.getUser()));
}
}
六.调用监听
在业务层使用上下文发布事件,参数传入前面创建的注册事件,监听方法就会被调用,由于前面创建的三种监听都依赖于RegistUserEvent,所以三个监听内的方法都会被调用执行
@RestController
public class RegistUserController {
Logger logger = LoggerFactory.getLogger(RegistUserController.class);
@Autowired
private RegistUserService registUserService;
@RequestMapping("/registUser")
public String registUser() {
User user = new User();
user.setUsername("test");
user.setPassword("123456");
registUserService.registUser(user);
return UUID.randomUUID().toString();
}
}
@Service
public class RegistUserService {
@Autowired
ApplicationContext applicationContext;
/**
* 注册用户
* @param user
* @return
*/
public String registUser(User user) {
// 发布事件
applicationContext.publishEvent(new RegistUserEvent(this, user));
return "success";
}
}
七.使用监听的注意事项:
①前两种方式创建的监听是无序执行的
②第三种方式实现接口SmartApplicationListener创建的监听方法必须在同步状态下才有效
③创建的监听,默认是同步执行的,换句话说,监听方法会阻塞主线程,必须所有监听方法完成后,才会反馈注册成功信息给用户(所以使用SmartApplicationListener创建的监听方法就不适用于解决业务解耦)
关于第三点怎么验证,观察代码,在监听方法中日志输出前,程序会睡眠5秒再输出日志,跑起程序会发现注册成功信息会在所有监听方法执行完成后才会反馈给用户,这样做哪里实现了业务的解耦?
只需在监听方法上添加注解@async,声明是异步方法即可,这个注解其实就是另起线程执行方法,所以就不会阻塞主进程的执行,这样就完成了业务的解耦。
备注:现在业务的解耦都使用MQ,MQ的功能远比监听强大的多,当然监听的作用还有很多,具体业务具体分析,选择最合适的功能来达成目的就可以。