2019独角兽企业重金招聘Python工程师标准>>>
什么是事件驱动模型
事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;理解它的几个关键点:
- 首先是一种对象间的一对多的依赖关系;
- 当一个对象的状态发生变化时,观察者(订阅者)都得到通知并做相应的处理;
- 观察者如何处理,目标无需干涉,松散耦合了它们之间的关系。
常见问题
比如说订单状态变化的时候,能够通知到邮件服务,短信服务,积分变化等等; 如果你是个新手,想象一下你去实现这个业务的代码怎么去实现?肯定是一个OrderService里面引入积分Service,短信Service,邮件Service,还有很多很多Service,可能还要调用第三方接口。是不是发现问题所在了,Service耦合严重,又是还会出现循环引用的问题,代码超长,以至于不方便维护。
从如上例子可以看出,应该使用一个观察者来解耦这些Service之间的依赖关系,如图:
图中增加了一个Listener来解耦OrderService和其他Service,即注册成功后,只需要通知相关的监听器,不需要关心它们如何处理,处理起来非常容易。这就是一个典型的事件处理模型-观察者模式,解耦目标对象和它的依赖对象,目标只需要通知它的依赖对象,具体怎么处理,依赖对象自己决定。比如是异步还是同步,延迟还是非延迟等。
其实上边其实也使用了DIP(依赖倒置原则),依赖于抽象,而不是具体。
还是就是使用了IOC思想,即以前主动去创建它依赖的Service,现在只是被动等待别人注册进来。
主要目的是:松散耦合对象间的一对多的依赖关系。
常用事件驱动模型
-
设计模式里面的观察者模式
-
JDK观察者模式
-
JavaBean事件驱动
-
spring事件驱动
-
......
JavaBean事件驱动
JavaBean规范提供了一种监听属性变化的事件驱动模型,提供操作JavaBean属性的类PropertyChangeSupport,PropertyEditorSupport以及PropertyChangeListener支持,PropertyEditorSupport就是目标,而PropertyChangeListener就是监听器。
Spring提供的事件
具体代表者是ApplicationEvent,其下有一个ApplicationContextEvent,表示Spring容器事件,且其又有如下实现:
- ContextStartedEvent:Spring容器启动后触发的事件;
- ContextStoppedEvent:Spring容器停止后触发的事件;
- ContextRefreshedEvent:Spring容器初始化或刷新完成后触发的事件;
- ContextClosedEvent:Spring容器关闭后触发的事件;
注:org.springframework.context.support.AbstractApplicationContext抽象类实现了LifeCycle的start和stop回调并发布ContextStartedEvent和ContextStoppedEvent事件。
事件发布具体代表者是:ApplicationEventPublisher及ApplicationEventMulticaster。
1、ApplicationContext接口继承了ApplicationEventPublisher,并在AbstractApplicationContext实现了具体代码,实际执行是委托给ApplicationEventMulticaster(可以认为是多播),我们常用的ApplicationContext都继承自AbstractApplicationContext,如ClassPathXmlApplicationContext、XmlWebApplicationContext等,所以自动拥有这个功能。
2、ApplicationContext自动到本地容器里找一个名字为”applicationEventMulticaster“的ApplicationEventMulticaster实现,如果没有自己new一个SimpleApplicationEventMulticaster,
监听器具体代表者是:ApplicationListener。其继承自JDK的EventListener,JDK要求所有监听器将继承它。
1. 它只提供了onApplicationEvent方法,我们需要在该方法实现内部判断事件类型来处理,或者指定某一个事件类型(泛型,实现ApplicationListener)。
2. 它没有提供按顺序触发监听器的语义,所以Spring提供了另一个接口SmartApplicationListener,该接口支持判断的事件类型、目标类型,及执行顺序。
由于在上一篇文章:使用事件驱动进行代码解耦-Spring篇,已经介绍了基于spring事件方式进行代码解耦的示例,这里主要介绍基于google guava来进行示例展示。
Guava事件机制
在guava中,事件处理器被称为事件总线EventBus,具体代表有EventBus类和AsyncEventBus类(异步);事件监听者被称为订阅者Subscriber。
1. 注册订阅者:新建一个事件总线EventBus/AsyncEventBus,就可以向其中注册订阅者,订阅者其实就是一个被标注了注解@com.google.common.eventbus.Subscribe的方法,向事件总线EventBus/AsyncEventBus注册订阅者的代码逻辑若下:
void register(Object listener) {
Multimap, Subscriber> listenerMethods = findAllSubscribers(listener);
for (Map.Entry, Collection> entry : listenerMethods.asMap().entrySet()) {
Class> eventType = entry.getKey();
Collection eventMethodsInListener = entry.getValue();
CopyOnWriteArraySet eventSubscribers = subscribers.get(eventType);
if (eventSubscribers == null) {
CopyOnWriteArraySet newSet = new CopyOnWriteArraySet();
eventSubscribers =
MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
}
eventSubscribers.addAll(eventMethodsInListener);
}
}
private Multimap, Subscriber> findAllSubscribers(Object listener) {
Multimap, Subscriber> methodsInListener = HashMultimap.create();
Class> clazz = listener.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class>[] parameterTypes = method.getParameterTypes();
Class> eventType = parameterTypes[0];
methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
}
return methodsInListener;
}
private static ImmutableList getAnnotatedMethodsNotCached(Class> clazz) {
Set extends Class>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
Map identifiers = Maps.newHashMap();
for (Class> supertype : supertypes) {
for (Method method : supertype.getDeclaredMethods()) {
if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
// TODO(cgdecker): Should check for a generic parameter type and error out
Class>[] parameterTypes = method.getParameterTypes();
checkArgument(
parameterTypes.length == 1,
"Method %s has @Subscribe annotation but has %s parameters."
+ "Subscriber methods must have exactly 1 parameter.",
method,
parameterTypes.length);
SubscriberRegistry.MethodIdentifier ident = new SubscriberRegistry.MethodIdentifier(method);
if (!identifiers.containsKey(ident)) {
identifiers.put(ident, method);
}
}
}
}
return ImmutableList.copyOf(identifiers.values());
}
从“if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic())”这一句代码可以明显的知道,一个bean向事件总线EventBus/AsyncEventBus进行注册,注册的并不是bean本身,而是该bean中被所有标注了注解@Subscribe的方法,一个被标注了注解@Subscribe的方法就是一个订阅者。当有事件发布到事件总线中,事件总线会遍历所有的订阅者进行事件处理。
2. 向事件总线EventBus和AsyncEventBus发布事件,直接调用事件总线的post方法即可
public void post(Object event) {
Iterator eventSubscribers = subscribers.getSubscribers(event);
if (eventSubscribers.hasNext()) {
dispatcher.dispatch(event, eventSubscribers);
} else if (!(event instanceof DeadEvent)) {
// the event had no subscribers and was not itself a DeadEvent
post(new DeadEvent(this, event));
}
}
Guava事件机制示例
本示例模拟订单生成与更新时会发送短信与站内信简单业务场景demo
1. 自定义一些接口以及POJO
/**
* 业务事件发布者
* @author dongsilin
* @version 1.0
* @date 2019/1/24
*/
public interface BizEventPublisher {
/**
* 发布同步事件
* @param eventData
*/
void publishEvent(Object eventData);
/**
* 发布异步事件
* @param eventData
*/
void publishEventAsync(Object eventData);
}
/**
* 业务事件发布者
* @author dongsilin
* @version 1.0
* @date 2019/1/24
*/
public interface BizEventPublisher {
/**
* 发布同步事件
* @param eventData
*/
void publishEvent(Object eventData);
/**
* 发布异步事件
* @param eventData
*/
void publishEventAsync(Object eventData);
}
/**
* 业务事件类型
* @author dongsilin
* @version 1.0
* @date 2019/1/24
*/
public enum BizEventType {
ORDER_CREATE("订单-创建"),
ORDER_UPDATE("订单-修改"),
;
String describe;
BizEventType(String describe) {
this.describe = describe;
}
}
/**
* @author dongsilin
* @version 1.0
* @date 2019/1/24
* 测试订单类
*/
@Data
public class Order {
private long orderId;
private long userId;
public Order(long orderId, long userId) {
this.setOrderId(orderId);
this.setUserId(userId);
}
}
2. 自定义业务数据,包含事件通用数据属性
/**
* @author dongsilin
* @version 1.0
* @date 2019/1/24
* 业务事件数据
*/
@Data
@AllArgsConstructor
public class BizEventData implements EventExecutor {
private BizEventType eventType;
private S data;
@Override
public void executeEvent(Consumer executor) {
executor.accept(data);
}
public static BizEventData of(BizEventType eventType, S data) {
return new BizEventData(eventType, data);
}
}
3. 业务事件发布配置管理
/**
* @author dongsilin
* @version 1.0
* @date 2019/1/24
* 业务事件发布配置管理
*/
@Slf4j
@Component
public class BizEventPublisherConfiguration implements BizEventPublisher {
/** 同步事件总线 */
private EventBus eventBus = new EventBus();
/** 异步事件总线 */
private AsyncEventBus eventBusAsync = new AsyncEventBus(
new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors() * 2,
5L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryBuilder().setNameFormat("guava-event-executor-pool-%d").build()
),
(Throwable e, SubscriberExceptionContext exceptionContext) -> {
log.error("", e);
}
);
@Override
public void publishEvent(Object eventData) {
eventBus.post(eventData);
}
@Override
public void publishEventAsync(Object eventData) {
eventBusAsync.post(eventData);
}
/**
* 构造器,注册监听者
* @param beanFactory
*/
public BizEventPublisherConfiguration (ListableBeanFactory beanFactory) {
// 获取所有带有 @BizEventListener 的 bean,将他们注册为监听者
beanFactory.getBeansWithAnnotation(BizEventListener.class)
.forEach((beanName, listener) -> {
eventBusAsync.register(listener);
eventBus.register(listener);
});
}
}
4. 业务事件监听配置管理
/**
* @author dongsilin
* @version 1.0
* @date 2019/1/24
* 业务事件监听配置管理
*/
@Slf4j
@Component
@BizEventListener
public class BizEventListenerConfiguration {
@Autowired
private TestMsgService msgService;
@Subscribe
public void executeEvent(BizEventData bizEventData) {
switch (bizEventData.getEventType()) {
// 订单创建,发送短信,.......
case ORDER_CREATE: bizEventData.executeEvent((data) -> {
msgService.sendPhoneMsg((Order) data);
});break;
// 订单修改,站内信提醒,.......
case ORDER_UPDATE: bizEventData.executeEvent((data) -> {
msgService.sendWebMsg((Order) data);
});break;
default: bizEventData.executeEvent((data) -> {
log.info("executeEvent bizEventData = {}", data);
});
}
}
}
5. 定义两个测试service,TestOrderService 和 TestMsgService
/**
* @author dongsilin
* @version 1.0
* @date 2019/1/24
*/
@Slf4j
@Service
public class TestOrderService {
@Autowired
private BizEventPublisher bizEventPublisher;
public void create(Order order) {
......
log.info("TestOrderService create order = {}", order);
bizEventPublisher.publishEvent(BizEvent.of(BizEventType.ORDER_CREATE, order));
}
public void update(Order order) {
......
log.info("TestOrderService update order = {}", order);
bizEventPublisher.publishEventAsync(BizEvent.of(BizEventType.ORDER_UPDATE, order));
}
}
/**
* @author dongsilin
* @version 1.0
* @date 2019/1/24
*/
@Slf4j
@Service
public class TestMsgService {
public void sendPhoneMsg(Order order) {
......
log.info("TestMsgService sendPhoneMsg order = {}", order);
}
public void sendWebMsg(Order order) {
......
log.info("TestMsgService sendWebMsg order = {}", order);
}
}
此处TestOrderService 中并没有强制依赖注入TestMsgService,而是通过发布事件的方式将事件数据发布到事件管理中心,由事件管理者来统一管理事件处理方式,解决了各个业务service严重耦合的场景,实现软件开发中的“高内聚-低耦合”原则。
通过如上,大体了解了google guava的事件机制,可以使用该机制非常简单的完成事件流程。