目录
2、利用Spring ApplicationListener实现异步非阻塞
二、根据应用场景的不同,观察者模式会对应不同的代码实现方式:
Guava EventBus框架:支持异步非阻塞模式,同时也支持同步阻塞模式
一、观察者模式:
在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
1、同步阻塞的实现方式:
比如:当用户注册成功以后,我们需要为用户发放:感谢信,优惠券, 体验金 等等,注册的后续操作频繁修改。这种情况下就需要用到观察者模式。
如以下代码,该种实现方式属于 。
观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。对照上面讲到的用户注册的例子,register() 函数依次调用执行每个观察者的 handleRegSuccess() 函数,等到都执行完成之后,才会返回结果给客户端。
public interface RegObserver {
void handleRegSuccess(long userId);
}
public class RegPromotionObserver implements RegObserver {
private PromotionService promotionService; // 依赖注入
@Override
public void handleRegSuccess(long userId) {
promotionService.issueNewUserExperienceCash(userId);
}
}
public class RegNotificationObserver implements RegObserver {
private NotificationService notificationService;
@Override
public void handleRegSuccess(long userId) {
notificationService.sendInboxMessage(userId, "Welcome...");
}
}
public class UserController {
private UserService userService; // 依赖注入
private List<RegObserver> regObservers = new ArrayList<>();
// 一次性设置好,之后也不可能动态的修改
public void setRegObservers(List<RegObserver> observers) {
regObservers.addAll(observers);
}
public Long register(String telephone, String password) {
//省略输入参数的校验代码
//省略userService.register()异常的try-catch代码
long userId = userService.register(telephone, password);
for (RegObserver observer : regObservers) {
observer.handleRegSuccess(userId);
}
return userId;
}
}
2、利用Spring ApplicationListener实现异步非阻塞
ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类或ApplicationListener接口,可以实现ApplicationContext事件处理。
// 发布事件
public void publishEvent(Object resource){
if (resource == null) return ;
threadPoolConfigure.getCustomThreadPool().start(() -> {
log.info("异步线程启动,resource-{}", resource);
try {
publish.publishEvent(new EventObject(resource));
} catch (Exception e) {
log.error("执行任务失败,异常信息-{}", e);
}
});}}
// 监听事件
public class EventListener implements ApplicationListener<EventObject> {
@Override
public void onApplicationEvent(EventObject bizEventObject) {
log.info("监听到消息");
Object source = bizEventObject.getSource();
if (source==null) {
log.error("发布事件值为null");
return ;
}
if (source instanceof BizEventDO){
bizEventManager.bizHandle(BizEventDO.class.cast(source));
} else if(source instanceof UserChangeEventDO){
//用户事件处理
userEventManager.bizProcess(UserChangeEventDO.class.cast(source));
} else if(source instanceof CustomerMessageEventDO){
//推送客户消息
customerMessageEventContext.singletonPush(CustomerMessageEventDO.class.cast(source));
} else if(source instanceof UserLoginInfoDTO){
//保存用户登录信息
userLoginInfoService.saveLoginInfo(UserLoginInfoDTO.class.cast(source));
} else if(source instanceof AccountEventDO){
//账户变更事件
accountEventContext.process(AccountEventDO.class.cast(source));
}
}
}
二、根据应用场景的不同,观察者模式会对应不同的代码实现方式:
- 同步阻塞的实现方式;同步阻塞是最经典的实现方式,主要是为了代码解耦;
- 异步非阻塞的实现方式;异步非阻塞除了能实现代码解耦之外,还能提高代码的执行效率;
- 进程内的实现方式;
- 跨进程的实现方式;进程间的观察者模式解耦更加彻底,一般是基于消息队列来实现,用来实现不同进程间的被观察者和观察者之间的交互。
三、异步非阻塞的观察者模式
基于以上的以上的实现方式,可以通过两种修改,使其变成异步非阻塞的观察者模式
- 在每个 handleRegSuccess() 函数中创建一个新的线程执行代码逻辑; 存在频繁创建和销毁线程,比较耗时,并且无法控制并发线程数,创建过多时可能到时堆栈溢出
- 在 UserController 的 register() 函数中使用线程池来执行每个观察者的 handleRegSuccess() 函数。尽管利用了线程池解决了第一种实现方式的问题,但线程池、异步执行逻辑都耦合在了 register() 函数中,增加了这部分业务代码的维护成本。
// 第一种实现方式,其他类代码不变,就没有再重复罗列
public class RegPromotionObserver implements RegObserver {
private PromotionService promotionService; // 依赖注入
@Override
public void handleRegSuccess(Long userId) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
promotionService.issueNewUserExperienceCash(userId);
}
});
thread.start();
}
}
// 第二种实现方式,其他类代码不变,就没有再重复罗列
public class UserController {
private UserService userService; // 依赖注入
private List<RegObserver> regObservers = new ArrayList<>();
private Executor executor;
public UserController(Executor executor) {
this.executor = executor;
}
public void setRegObservers(List<RegObserver> observers) {
regObservers.addAll(observers);
}
public Long register(String telephone, String password) {
//省略输入参数的校验代码
//省略userService.register()异常的try-catch代码
long userId = userService.register(telephone, password);
for (RegObserver observer : regObservers) {
executor.execute(new Runnable() {
@Override
public void run() {
observer.handleRegSuccess(userId);
}
});
}
return userId;
}
}
Guava EventBus框架:支持异步非阻塞模式,同时也支持同步阻塞模式
1、EventBus、AsyncEventBus
EventBus 实现了同步阻塞的观察者模式
AsyncEventBus 继承自 EventBus,提供了异步非阻塞的观察者模式。
具体的使用方式:
EventBus eventBus = new EventBus(); // 同步阻塞模式
EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(8));// 异步阻塞模式
2、register() 函数
EventBus 类提供了 register() 函数用来注册观察者。
3、unregister() 函数
相对于 register() 函数,unregister() 函数用来从 EventBus 中删除某个观察者。
4、post() 函数
EventBus 类提供了 post() 函数,用来给观察者发送消息。
public void post(Object event);
跟经典的观察者模式的不同之处在于,当我们调用 post() 函数发送消息的时候,并非把消息发送给所有的观察者,而是发送给可匹配的观察者。所谓可匹配指的是,能接收的消息类型是发送消息(post 函数定义中的 event)类型的父类。
举例:AObserver 能接收的消息类型是 XMsg,BObserver 能接收的消息类型是 YMsg,CObserver 能接收的消息类型是 ZMsg。其中,XMsg 是 YMsg 的父类。
XMsg xMsg = new XMsg();
YMsg yMsg = new YMsg();
ZMsg zMsg = new ZMsg();
post(xMsg); => AObserver接收到消息
post(yMsg); => AObserver、BObserver接收到消息
post(zMsg); => CObserver接收到消息
5、@Subscribe 注解
EventBus 通过 @Subscribe 注解来标明,某个函数能接收哪种类型的消息。
public DObserver {
//...省略其他属性和方法...
@Subscribe
public void f1(PMsg event) { //... }
@Subscribe
public void f2(QMsg event) { //... }
}
通過eventBus实现之前的 观察者模式
public class UserController {
private UserService userService; // 依赖注入
private EventBus eventBus;
private static final int DEFAULT_EVENTBUS_THREAD_POOL_SIZE = 20;
public UserController() {
//eventBus = new EventBus(); // 同步阻塞模式
eventBus = new AsyncEventBus(Executors.newFixedThreadPool(DEFAULT_EVENTBUS_THREAD_POOL_SIZE)); // 异步非阻塞模式
}
public void setRegObservers(List<Object> observers) {
for (Object observer : observers) {
eventBus.register(observer); // 用来注册观察者
}
}
public Long register(String telephone, String password) {
//省略输入参数的校验代码
//省略userService.register()异常的try-catch代码
long userId = userService.register(telephone, password);
eventBus.post(userId); //用来给观察者发送消息
return userId;
}
}
public class RegPromotionObserver {
private PromotionService promotionService; // 依赖注入
@Subscribe // 注釋該方法可以接受long 類型的消息
public void handleRegSuccess(Long userId) {
promotionService.issueNewUserExperienceCash(userId);
}
}
public class RegNotificationObserver {
private NotificationService notificationService;
@Subscribe // 注釋該方法可以接受long 類型的消息
public void handleRegSuccess(Long userId) {
notificationService.sendInboxMessage(userId, "...");
}
}
四、跨进程的观察者模式 - 消息队列
需要引入中間系統:消息隊列