一.概述
監聽的用途很多,根據業務需要可以選擇適當的監聽來完成想要處理的功能,這裏使用監聽來完成業務的解耦。
用戶註冊流程:
①數據入庫 -> ②發送激活用戶郵件 -> ③贈送初始積分
當用戶註冊時,主流程其實只需完成①數據入庫,然後返回註冊成功信息給用戶,主流程其實就可以結束了,如果②和③也跟①寫到一起,那麼註冊耗時將變長,用戶體驗變差,這時需要進行業務的拆分,主流程只需要完成①,子流程②和③拆分出來。使用監聽器,當應用監聽到用戶註冊事件,單獨去完成②和③,從而達到不阻塞主流程的目的。
截圖是僞代碼,需要你腦補一下實際代碼。。。😄
二.創建事件
監聽是圍繞着事件進行處理的,所以需要先創建用戶註冊的事件,繼承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的功能遠比監聽強大的多,當然監聽的作用還有很多,具體業務具體分析,選擇最合適的功能來達成目的就可以。