`

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

 
阅读更多

1. 背景

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

 

2. spring中的事件驱动机制

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

写道
ApplicationContext通过ApplicationEvent类和ApplicationListener接口进行事件处理。
如果将实现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.总结

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

 

 

分享到:
评论

相关推荐

    领域驱动设计和开发实战.pdf

    使用Spring框架、Dozer等工具实现领域模型,并通过具体的代码示例展示了领域驱动开发的过程。 #### 八、结论 领域驱动设计不仅是一种技术手段,更是一种思维方式,它要求开发团队深入了解业务领域,构建出既符合...

    关于SpringCloud的各种示例代码分享.zip

    Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具集,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)...

    基于事件驱动的医疗设备管理系统设计.pdf

    依赖注入机制简化了业务对象替换的过程,提高了组件间的解耦,有利于代码重构。面向切面编程(AOP)进一步增强了系统灵活性,允许在运行时动态插入或修改行为,适应事件驱动的需求。 【系统特点与优势】 - **系统性...

    spring-integration-reference

    - **TaskScheduler和Trigger**:新增了任务调度器和触发器的支持,以便开发者能够更方便地处理定时任务和事件驱动的应用场景。 - **RestTemplate和HttpMessageConverter**:为了简化HTTP请求的处理,Spring ...

    java_spring_day05.pdf

    2. **代码重构:** - 清理旧代码,优化代码结构,提高代码可读性和可维护性。 - 使用更好的设计模式和技术来重构代码。 3. **测试覆盖:** - 重构过程中,应编写足够的单元测试和集成测试,确保系统的稳定性。 ...

    spring重点知识_ioc_springjava_aop_

    Spring框架是Java开发中不可或缺的一部分,它以其强大的依赖注入(IOC)和面向切面编程(AOP)功能闻名。在本文中,我们将深入探讨这些核心概念,并了解它们如何协同工作,提升应用程序的可维护性和可扩展性。 **...

    struts2和spring3注解整合问题

    Struts2和Spring3是两个非常流行的Java Web框架,它们分别负责MVC模式中的Action层和依赖注入(DI)及面向切面编程(AOP)。将这两个框架整合在一起可以实现更高效、更灵活的Web应用开发。然而,在整合过程中,可能...

    prjSpringdataEmpmanager.rar

    本项目“prjSpringdataEmpmanager.rar”是一个使用Spring Boot和Spring Data JPA技术重构的员工管理系统,旨在提高系统的开发效率和数据管理能力。 一、Spring Boot简介 Spring Boot是由Pivotal团队提供的全新框架...

    领域驱动设计和开发实战

    领域驱动设计(DDD)是一种软件开发方法,旨在通过将业务领域的概念与软件实现紧密关联,以提高软件系统的业务契合度和可维护性。Eric Evans的《领域驱动设计》一书为DDD奠定了理论基础,强调了建立共享的通用语言、...

    SpringCloud笔记

    - 可靠性差:一处故障可能导致整个应用崩溃。 - 扩展困难:整体升级时必须停机,影响用户体验。 - 技术创新障碍:新功能引入往往受限于现有技术栈。 3. **微服务架构优势**: - **简化服务**:每个服务仅负责一...

    Spring Boot面试题(2022最新版)-重点

    通过采用特定的方式配置 Spring,Spring Boot 可以自动配置 Spring 和第三方库框架,极大地简化了开发流程,让开发者能够专注于编写业务逻辑代码。 **1.2 Spring Boot 的优点有哪些?** - **易于上手:** 提供了一...

    spring in action 第三版(英文)

    Spring框架官网指出,通过本书的学习,读者将学会如何利用Spring编写更简单、更容易维护的代码,从而能够专注于解决业务需求等关键问题。JavaLobby.org的评价同样为五星,认为本书内容丰富而易读。此外,来自多个...

    Agile Java Development With Spring, Hibernate and Eclipse

    在《敏捷Java开发:Spring、Hibernate与Eclipse》这一主题中,作者Anil Hemrajani介绍了如何利用敏捷方法结合Java技术来构建企业级应用。本节重点介绍敏捷Java开发的概念、目的以及它如何与Spring、Hibernate和...

    Agile Java Development with Spring, Hibernte and Eclipse

    综上所述,《敏捷Java开发》是一本全面介绍Spring、Hibernate框架和Eclipse IDE在敏捷Java开发中的应用的书籍,涵盖了从技术细节到开发实践的各个方面,非常适合希望提升Java开发技能的读者阅读。

    POJO IN ACTION

    - **依赖注入**:Spring的核心特性之一是依赖注入,它允许将POJO注入到应用程序中,从而避免了硬编码的依赖关系。 - **AOP(面向切面编程)**:Spring支持AOP,可以在不修改POJO的情况下添加横切关注点。 **...

    Aggregate-Auction:总体拍卖练习,以解决软件Craft.io培训中的依赖性破坏,代码气味和重构部分

    在Java中,Spring框架提供了强大的依赖注入功能,可以用来管理对象间的依赖关系,使得代码更易于测试和维护。 2. **代码气味**:代码气味是指代码中可能预示着潜在问题的迹象,它们不是错误,但可能导致复杂性增加...

    ssm学习记录二

    在"ssm学习记录二"中,可能还涉及到了代码重构、异常处理、日志记录等方面的内容。在不断完善的代码中,开发者可能在实践中遇到了问题并找到了解决方案,这些都是宝贵的学习经验。对于初学者来说,理解和掌握SSM框架...

    轻量级J2EE企业开发应用

    - Spring 引入了一套统一的异常处理机制,可以将 Hibernate 抛出的各种 Checked Exception 转换为 Spring 自身的一系列 RuntimeExceptions。这种方式不仅简化了异常处理逻辑,还提升了系统的可读性和可维护性。 5...

Global site tag (gtag.js) - Google Analytics