今天,我们来简单谈谈事件驱动。 其实写这篇文章让我很烦恼。 因为事件驱动这个名词,我经常找不到定性的说明,担心自己的表达是不是错了。 说起事件驱动,你可能会马上联想到这么多概念。 观察者模式、订阅模式、消息队列MQ、消息驱动、事件、事件源……为了不产生歧义,我知道这些歧义
在设计模式中,观察者模式可以说是非常典型的行动型设计模式。 猫叫、主人醒来、老鼠逃跑的典型例子就是事件驱动模型在设计层面的体现。
另一种模式,发布订阅模型往往与观察者模型相同,但我的理解是,两者唯一的区别在于发布订阅模型需要调度中心,而不需要观察者模型。 例如,观察者名单可以直接由被观察者维护。 但两者即使混用、相互替代,通常也不影响表达。
MQ,中间件级别的消息队列(e.g. ActiveMQ,RabbitMQ ) )被认为是发布订阅模型的具体体现。 事件驱动-发布订阅-MQ,从抽象到具体。
java和spring提供了对Event的抽象,分别表示在语言和三方框架级别对事件的支持。
EventSourcing这一概念与领域驱动设计相关,DDD在事件驱动中也非常受欢迎,领域对象的状态完全由事件驱动控制,由此派生出了CQRS体系结构。 具体的实现框架是axon框架。
Nginx可以用作高性能的APP应用服务器(e.g. openResty )或Nodejs事件驱动特性。 这些也是事件驱动的出现。
本文涵盖的内容主要有前面四点。
对事件的Spring支持
在Spring文档的Event支持翻译之后,说明:
应用程序上下文通过应用程序事件类和应用程序监听器接口处理事件。 如果将实现application监听器接口的bean注入上下文中,则每次使用ApplicationContext发布ApplicationEvent时,它都会通知您。 从本质上说,这是标准的观察者设计模式。
从Spring4.2开始,可以使用任何java对象获得与注解类似的效果。 首先,我们来看看不正确的注解如何在spring中使用事件驱动机制。
业务需求定义:用户注册后,系统需要向用户发送邮件告知用户注册成功,并对用户进行积分初始化; 隐式设计要求在用户注册后,可能会在后续要求中添加其他操作。 例如,我们希望程序满足可扩展性和开关原则,例如再发送一条消息。
如果不使用事件驱动,代码可能与以下内容类似:
公共类用户服务{
@Autowired
电子邮件服务电子邮件服务
@Autowired
ScoreService scoreService;
@Autowired
OtherService otherService;
公共语音注册器(string name ) {
System.out.println ('用户:“name”已注册!' );
电子邮件服务. send电子邮件(name;
scoreservice.initscore(name;
otherservice.execute(name;
}
}
要说有什么缺点,其实也不能说有。 因为很多人可能喜欢在开发中这样写,写同步代码。 但是,这样写的话,实际上并不满足隐含的设计要求,假设添加更多的注册项目服务,就需要修改register的方法,让对应的服务注入UserService。 实际上,register并不关心这些“多余的”操作,如何提取这些多余的代码呢? 可以使用Spring提供的事件机制。
定义用户注册事件
publicclassuserregistereventextendsapplicationevent {
publicuserregisterevent (string name ) { //name即source
super(name;
}
}
请注意,ApplicationEvent是Spring提供的所有Event类的基类,为了简单起见,注册事件可以传递name (复杂的对象,但要理解序列化机制。
用户注册服务定义(事件发布者)
@Service //1
publicclassuserserviceimplementsapplicationeventpublisheraware {//2}
公共语音注册器(string name ) {
System.out.println ('用户:“name”已注册!'
);applicationEventPublisher.publishEvent(new UserRegisterEvent(name));// <3>
}
private ApplicationEventPublisher applicationEventPublisher; // <2>
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { // <2>
this.applicationEventPublisher = applicationEventPublisher;
}
}
<1> 服务必须交给Spring容器托管
<2> ApplicationEventPublisherAware是由Spring提供的用于为Service注入ApplicationEventPublisher事件发布器的接口,使用这个接口,我们自己的Service就拥有了发布事件的能力。
<3> 用户注册后,不再是显示调用其他的业务Service,而是发布一个用户注册事件。
定义邮件服务,积分服务,其他服务(事件订阅者)
@Service // <1>
public class EmailService implements ApplicationListener { // <2>
@Override
public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
System.out.println("邮件服务接到通知,给 " + userRegisterEvent.getSource() + " 发送邮件...");// <3>
}
}
<1> 事件订阅者的服务同样需要托管于Spring容器
<2> ApplicationListener接口是由Spring提供的事件订阅者必须实现的接口,我们一般把该Service关心的事件类型作为泛型传入。
<3> 处理事件,通过event.getSource()即可拿到事件的具体内容,在本例中便是用户的姓名。
其他两个Service,也同样编写,实际的业务操作仅仅是打印一句内容即可,篇幅限制,这里省略。
编写启动类
@SpringBootApplication
@RestController
public class EventDemoApp {
public static void main(String[] args) {
SpringApplication.run(EventDemoApp.class, args);
}
@Autowired
UserService userService;
@RequestMapping("/register")
public String register(){
userService.register("kirito");
return "success";
}
}
当我们调用userService.register(“kirito”);方法时,控制台打印信息如下:
他们的顺序是无序的,如果需要控制顺序,需要重写order接口,这点不做介绍。其次,我们完成了用户注册和其他服务的解耦,这也是事件驱动的最大特性之一,如果需要在用户注册时完成其他操作,只需要再添加相应的事件订阅者即可。
Spring 对Event的注解支持
上述的几个接口已经非常清爽了,如果习惯使用注解,Spring也提供了,不再需要显示实现
注解式的事件发布者
@Service
public class UserService {
public void register(String name) {
System.out.println("用户:" + name + " 已注册!");
applicationEventPublisher.publishEvent(new UserRegisterEvent(name));
}
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
}
Spring4.2之后,ApplicationEventPublisher自动被注入到容器中,采用Autowired即可获取。
注解式的事件订阅者
@Service
public class EmailService {
@EventListener
public void listenUserRegisterEvent(UserRegisterEvent userRegisterEvent) {
System.out.println("邮件服务接到通知,给 " + userRegisterEvent.getSource() + " 发送邮件...");
}
}
@EventListener注解完成了ApplicationListener接口的使命。
更多的特性可以参考SpringFramework的文档。
Spring中事件的应用
在以往阅读Spring源码的经验中,接触了不少使用事件的地方,大概列了以下几个,加深以下印象:
Spring Security中使用AuthenticationEventPublisher处理用户认证成功,认证失败的消息处理。
public interface AuthenticationEventPublisher {
void publishAuthenticationSuccess(Authentication authentication);
void publishAuthenticationFailure(AuthenticationException exception,
Authentication authentication);
}
Hibernate中持久化对象属性的修改是如何被框架得知的?正是采用了一系列持久化相关的事件,如DefaultSaveEventListener,DefaultUpdateEventListener,事件非常多,有兴趣可以去org.hibernate.event包下查看。
Spring Cloud Zuul中刷新路由信息使用到的ZuulRefreshListener
private static class ZuulRefreshListener implements ApplicationListener {
...
public void onApplicationEvent(ApplicationEvent event) {
if(!(event instanceof ContextRefreshedEvent) && !(event instanceof RefreshScopeRefreshedEvent) && !(event instanceof RoutesRefreshedEvent)) {
if(event instanceof HeartbeatEvent && this.heartbeatMonitor.update(((HeartbeatEvent)event).getValue())) {
this.zuulHandlerMapping.setDirty(true);
}
} else {
this.zuulHandlerMapping.setDirty(true);
}
}
}
Spring容器生命周期相关的一些默认Event
ContextRefreshedEvent,ContextStartedEvent,ContextStoppedEvent,ContextClosedEvent,RequestHandledEvent
。。。其实吧,非常多。。。
总结
本文暂时只介绍了Spring中的一些简单的事件驱动机制,相信如果之后再看到Event,Publisher,EventListener一类的单词后缀时,也能立刻和事件机制联系上了。再阅读Spring源码时,如果发现出现了某个Event,但由于不是同步调用,所以很容易被忽视,我一般习惯下意识的去寻找有没有提供默认的Listener,这样不至于漏掉一些“隐藏”的特性。下一篇文章打算聊一聊分布式场景下,事件驱动使用的注意点。
公众号刚刚创立,如果觉得文章不错,希望能分享到您的朋友圈,如果对文章有什么想法和建议,可以与我沟通。