欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一次业务代码重构的总结:spring中事件驱动机制

程序员文章站 2022-07-13 11:11:57
...

1. 背景

       最近一个多月的事件,加班加点的忙着项目上线,为了赶进度,也没过多的要求兄弟们编写的程序,只要保证线上质量就行,没强调程序的可扩展性及程序设计方面的细节。现在业务稳定了,梳理了整个项目,利用spring中的事件驱动机制做了一次重构。这里,只做个简单的总结,希望能帮到有需要的小伙伴。

 

2. spring中的事件驱动机制

      spring的官方wiki上对事件(Event)是这样描述(翻译成中文)的:

写道
ApplicationContext通过ApplicationEvent类和ApplicationListener接口进行事件处理。
如果将实现ApplicationListener接口的bean注入到上下文中,
则每次使用ApplicationContext发布ApplicationEvent时,都会通知该bean。
本质上,这是标准的观察者设计模式。

 

       这里关于观察者设计模式 做个简单的描述:在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。

       喜欢看UML的小伙伴可以参考下面的图片:

一次业务代码重构的总结:spring中事件驱动机制
            
    
    博客分类: Spring设计模式  

       在设计模式中,观察者模式可以算得上是一个非常经典的行为型设计模式。举个通俗的例子:猫叫了,主人醒了,老鼠跑了,这一经典的例子,是事件驱动模型在设计层面的体现。

       有了解过观察者模式的小伙伴,经常会和发布订阅模式进行对比,这里做个简单的比较,发布订阅模式往往被人们等同于观察者模式,但小弟的理解是两者唯一区别,是发布订阅模式需要有一个调度中心,而观察者模式不需要,例如观察者的列表可以直接由被观察者维护。不过两者即使被混用,互相替代,通常不影响表达。

       熟悉中间件的小伙伴都知道,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";
  }
}

 

  • 测试

一次业务代码重构的总结:spring中事件驱动机制
            
    
    博客分类: Spring设计模式  

一次业务代码重构的总结:spring中事件驱动机制
            
    
    博客分类: Spring设计模式   

 

 

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("笑傲江湖");

一次业务代码重构的总结:spring中事件驱动机制
            
    
    博客分类: Spring设计模式  

一次业务代码重构的总结:spring中事件驱动机制
            
    
    博客分类: Spring设计模式  

 

 

4.总结

  1. java和spring中都拥有Event的抽象,分别代表了语言级别和三方框架级别对事件的支持。
  2. EventSourcing这个概念就要关联到领域驱动设计,DDD对事件驱动也是非常地青睐,领域对象的状态完全是由事件驱动来控制,由其衍生出了CQRS架构,具体实现框架有AxonFramework。
  3. Nginx可以作为高性能的应用服务器(e.g. openResty),以及Nodejs事件驱动的特性,这些也是都是事件驱动的体现。