一次业务代码重构的总结:spring中事件驱动机制
1. 背景
最近一个多月的事件,加班加点的忙着项目上线,为了赶进度,也没过多的要求兄弟们编写的程序,只要保证线上质量就行,没强调程序的可扩展性及程序设计方面的细节。现在业务稳定了,梳理了整个项目,利用spring中的事件驱动机制做了一次重构。这里,只做个简单的总结,希望能帮到有需要的小伙伴。
2. spring中的事件驱动机制
spring的官方wiki上对事件(Event)是这样描述(翻译成中文)的:
如果将实现ApplicationListener接口的bean注入到上下文中,
则每次使用ApplicationContext发布ApplicationEvent时,都会通知该bean。
本质上,这是标准的观察者设计模式。
这里关于观察者设计模式 做个简单的描述:在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。
喜欢看UML的小伙伴可以参考下面的图片:
在设计模式中,观察者模式可以算得上是一个非常经典的行为型设计模式。举个通俗的例子:猫叫了,主人醒了,老鼠跑了,这一经典的例子,是事件驱动模型在设计层面的体现。
有了解过观察者模式的小伙伴,经常会和发布订阅模式进行对比,这里做个简单的比较,发布订阅模式往往被人们等同于观察者模式,但小弟的理解是两者唯一区别,是发布订阅模式需要有一个调度中心,而观察者模式不需要,例如观察者的列表可以直接由被观察者维护。不过两者即使被混用,互相替代,通常不影响表达。
熟悉中间件的小伙伴都知道,MQ(中间件级别的消息队列,例如:ActiveMQ,RabbitMQ),可以认为是发布订阅模式的一个具体体现。这是一个从抽象到具体的过程,即:事件驱动,发布订阅,MQ。
此外,除了spring中有Event的抽象之外,还有很多概念都是事件驱动的体现,本文会在最后的总结中,做个简单的描述。
3. 简单的示例
在介绍demo之前,先说明下本demo中依赖的spring的版本:5.0.7.RELEASE。这里为什么要说明下spring的版本呢,是因为spring 4.2以后的版本,提供了注解式的支持,我们可以使用任意的java对象配合注解达到同样的效果。为了遵循公司的保密原则,所以采用一个通用的业务需求,作为demo示例的业务背景。需求如下:
先按照我们team内部的程序设计风格,实现下这个需求:
@Service public class UserService { @Autowired private EmailService emailService; @Autowired private ScoreService scoreService; @Autowired private OtherService otherService; public void register(final String name) { System.out.println("用户:" + name + " 已注册!"); emailService.sendEmail(name, "register email"); scoreService.initScore(name, 10000d); otherService.otherBizExecute(name, "other biz"); } }
@Service public class EmailService { public void sendEmail(final String name, final String email) { System.out.println("user:" + name + ", send email:" + email); } }
@Service public class ScoreService { public void initScore(final String name, final Double score) { System.out.println("user:" + name + ", init score:" + score); } }
@Service public class OtherService { public void otherBizExecute(final String name, final String otherBiz) { System.out.println("user:" + name + ", otherBiz:" + otherBiz); } }
这种程序设计的方式,要说有什么毛病,其实也不算有,因为可能大多数人在开发中都会这么写,喜欢写同步代码。但这么写,实际上并不是特别的符合隐含的设计需求,假设增加更多的注册项service,我们需要修改register的方法,并且让UserService注入对应的Service。而实际上,register并不关心这些“额外”的操作,如何将这些多余的代码抽取出去呢?便可以使用Spring提供的Event机制。
下面我会提供使用注解和不适用注解两种形式的demo示例。
3.1 使用注解
- 定义用户注册事件
public class UserRegisterEvent extends ApplicationEvent { /** * ApplicationEvent是由Spring提供的所有Event类的基类, * 为了简单起见,注册事件的source调用方只传递name, * 也可以是复杂的对象,但注意要了解清楚序列化机制 * * @param source */ public UserRegisterEvent(final Object source) { super(source); } }
- 定义注解式的事件发布者
@Service public class UserService { /** * Spring4.2之后,ApplicationEventPublisher自动被注入到容器中, * 采用Autowired即可获取 */ @Autowired private ApplicationEventPublisher applicationEventPublisher; public void register(final String name) { System.out.println("用户:" + name + " 已注册!"); applicationEventPublisher.publishEvent(new UserRegisterEvent(name)); } }
- 注解式事件订阅者
@Service public class EmailService { @EventListener public void listenerUserRegisterEvent(final UserRegisterEvent userRegisterEvent) { System.out.println("邮件服务接到通知,给:" + userRegisterEvent.getSource() + "发送邮件....."); } }
@Service public class ScoreService { @EventListener public void listenerUserRegister(final UserRegisterEvent userRegisterEvent) { System.out.println("给用户:" + userRegisterEvent.getSource() + "初始化了1000积分"); } }
@Service public class OtherService { @EventListener public void listenerUserRegister(final UserRegisterEvent userRegisterEvent) { System.out.println("用户:" + userRegisterEvent.getSource() + "发起了其他业务操作"); } }
- 服务启动类
@SpringBootApplication @RestController public class SpringEventDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringEventDemoApplication.class, args); } @Autowired private UserService userService; @RequestMapping("/user/register") public String register(){ userService.register("张三丰"); return "success"; } }
- 测试
3.2 不使用注解
这里,只需要将3.1中的UserService,EmailService,ScoreService,OtherService进行修改即可:
- 事件发布者
/** * 1.UserService必须交给Spring容器托管 * * 2.ApplicationEventPublisherAware是由Spring提供的用于为Service * 注入ApplicationEventPublisher事件发布器的接口,使用这个接口, * 我们自己的Service就拥有了发布事件的能力 * * 3.用户注册后,不再是显示调用其他的业务Service, * 而是发布一个用户注册事件 * */ @Service public class UserService implements ApplicationEventPublisherAware { public void register(String name) { System.out.println("用户:" + name + " 已注册!"); applicationEventPublisher.publishEvent(new UserRegisterEvent(name)); } private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher( ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } }
- 事件订阅者
@Service public class EmailService implements ApplicationListener<UserRegisterEvent> { /** * 1.事件订阅者的服务同样需要托管于Spring容器 * * 2.ApplicationListener<E extends ApplicationEvent>接口 * 是由Spring提供的事件订阅者必须实现的接口,我们一般把 * Service关心的事件类型作为泛型传入 * * 3. 处理事件,通过event.getSource()即可拿到事件的具体内容,在本例中便是用户的姓名 * * @param userRegisterEvent */ @Override public void onApplicationEvent(final UserRegisterEvent userRegisterEvent) { System.out.println("邮件服务接到通知,给:" + userRegisterEvent.getSource() + "发送邮件....."); } }
@Service public class ScoreService implements ApplicationListener<UserRegisterEvent> { /** * 1.事件订阅者的服务同样需要托管于Spring容器 * <p> * 2.ApplicationListener<E extends ApplicationEvent>接口 是由Spring提供的事件订阅者必须实现的接口,我们一般把 Service关心的事件类型作为泛型传入 * <p> * 3. 处理事件,通过event.getSource()即可拿到事件的具体内容,在本例中便是用户的姓名 * * @param userRegisterEvent */ @Override public void onApplicationEvent(final UserRegisterEvent userRegisterEvent) { System.out.println("给用户:" + userRegisterEvent.getSource() + "初始化了1000积分"); } }
@Service public class OtherService implements ApplicationListener<UserRegisterEvent> { /** * 1.事件订阅者的服务同样需要托管于Spring容器 * <p> * 2.ApplicationListener<E extends ApplicationEvent>接口 是由Spring提供的事件订阅者必须实现的接口,我们一般把 Service关心的事件类型作为泛型传入 * <p> * 3. 处理事件,通过event.getSource()即可拿到事件的具体内容,在本例中便是用户的姓名 * * @param userRegisterEvent */ @Override public void onApplicationEvent(final UserRegisterEvent userRegisterEvent) { System.out.println("用户:" + userRegisterEvent.getSource() + "发起了其他业务操作"); } }
- 测试
利用3.1的服务启动类SpringEventDemoApplication,修改一行代码
userService.register("笑傲江湖");
4.总结
- java和spring中都拥有Event的抽象,分别代表了语言级别和三方框架级别对事件的支持。
- EventSourcing这个概念就要关联到领域驱动设计,DDD对事件驱动也是非常地青睐,领域对象的状态完全是由事件驱动来控制,由其衍生出了CQRS架构,具体实现框架有AxonFramework。
- Nginx可以作为高性能的应用服务器(e.g. openResty),以及Nodejs事件驱动的特性,这些也是都是事件驱动的体现。