SpringBoot事件监听机制及观察者模式/发布订阅模式

简介: SpringBoot事件监听机制及观察者模式/发布订阅模式

本篇要点

  • 介绍观察者模式和发布订阅模式的区别。

  • SpringBoot快速入门事件监听。

什么是观察者模式?

观察者模式是经典行为型设计模式之一。

在GoF的《设计模式》中,观察者模式的定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。如果你觉得比较抽象,接下来这个例子应该会让你有所感觉:

就拿用户注册功能为例吧,假设用户注册成功之后,我们将会发送邮件,优惠券等等操作,很容易就能写出下面的逻辑:

@RestController
@RequestMapping("/user")
public class SimpleUserController {
   
   

    @Autowired
    private SimpleEmailService emailService;

    @Autowired
    private SimpleCouponService couponService;

    @Autowired
    private SimpleUserService userService;

    @GetMapping("/register")
    public String register(String username) {
   
   
        // 注册
        userService.register(username);
        // 发送邮件
        emailService.sendEmail(username);
        // 发送优惠券
        couponService.addCoupon(username);
        return "注册成功!";
    }
}

这样写会有什么问题呢?受王争老师启发:

  • 方法调用时,同步阻塞导致响应变慢,需要异步非阻塞的解决方案。

  • 注册接口此时做的事情:注册,发邮件,优惠券,违反单一职责的原则。当然,如果后续没有拓展和修改的需求,这样子倒可以接受。

  • 如果后续注册的需求频繁变更,相应就需要频繁变更register方法,违反了开闭原则。

针对以上的问题,我们想一想解决的方案:

  • 一、异步非阻塞的效果可以新开一个线程执行耗时的发送邮件任务,但频繁地创建和销毁线程比较耗时,并且并发线程数无法控制,创建过多的线程会导致堆栈溢出。

  • 二、使用线程池执行任务解决上述问题。

@Service
@Slf4j
public class SimpleEmailService {
   
   
  // 启动一个线程执行耗时操作
    public void sendEmail(String username) {
   
   
        Thread thread = new Thread(()->{
   
   
            try {
   
   
                // 模拟发邮件耗时操作
                Thread.sleep(3000);
            } catch (InterruptedException e) {
   
   
                e.printStackTrace();
            }
            log.info("给用户 [{}] 发送邮件...", username);
        });
        thread.start();
    }
}

@Slf4j
@Service
public class SimpleCouponService {
   
   

    ExecutorService executorService = Executors.newSingleThreadExecutor();
  // 线程池执行任务,减少资源消耗
    public void addCoupon(String username) {
   
   
        executorService.execute(() -> {
   
   
            try {
   
   
                Thread.sleep(3000);
            } catch (InterruptedException e) {
   
   
                e.printStackTrace();
            }
            log.info("给用户 [{}] 发放优惠券", username);
        });
    }
}

这里用户注册事件对【发送短信和优惠券】其实是一对多的关系,可以使用观察者模式进行解耦:

/**
 * 主题接口
 */
public interface Subject {
   
   

    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String message);
}

/**
 * 观察者接口
 */
public interface Observer {
   
   

    void update(String message);
}

@Component
@Slf4j
public class EmailObserver implements Observer {
   
   

    @Override
    public void update(String message) {
   
   
        log.info("向[{}]发送邮件", message);
    }
}
@Component
@Slf4j
public class CouponObserver implements Observer {
   
   

    @Override
    public void update(String message) {
   
   
        log.info("向[{}]发送优惠券",message);
    }
}

@Component
public class UserRegisterSubject implements Subject {
   
   

    @Autowired
    List<Observer> observers;

    @Override
    public void registerObserver(Observer observer) {
   
   
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
   
   
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String username) {
   
   
        for (Observer observer : observers) {
   
   
            observer.update(username);
        }
    }
}


@RestController
@RequestMapping("/")
public class UserController {
   
   

    @Autowired
    UserRegisterSubject subject;

    @Autowired
    private SimpleUserService userService;


    @GetMapping("/reg")
    public String reg(String username) {
   
   
        userService.register(username);
        subject.notifyObservers(username);
        return "success";
    }
}

发布订阅模式是什么?

观察者模式和发布订阅模式是有一点点区别的,区别有以下几点:

  • 前者:观察者订阅主题,主题也维护观察者的记录,而后者:发布者和订阅者不需要彼此了解,而是在消息队列或代理的帮助下通信,实现松耦合。

  • 前者主要以同步方式实现,即某个事件发生时,由Subject调用所有Observers的对应方法,后者则主要使用消息队列异步实现。

image.png

尽管两者存在差异,但是他们其实在概念上相似,网上说法很多,不需要过于纠结,重点在于我们需要他们为什么出现,解决了什么问题。

Spring事件监听机制概述

SpringBoot中事件监听机制则通过发布-订阅实现,主要包括以下三部分:

  • 事件 ApplicationEvent,继承JDK的EventObject,可自定义事件。

  • 事件发布者 ApplicationEventPublisher,负责事件发布。

  • 事件监听者 ApplicationListener,继承JDK的EventListener,负责监听指定的事件。

我们通过SpringBoot的方式,能够很容易实现事件监听,接下来我们改造一下上面的案例:

SpringBoot事件监听

定义注册事件

public class UserRegisterEvent extends ApplicationEvent {
   
   

    private String username;

    public UserRegisterEvent(Object source) {
   
   
        super(source);
    }

    public UserRegisterEvent(Object source, String username) {
   
   
        super(source);
        this.username = username;
    }

    public String getUsername() {
   
   
        return username;
    }
}

注解方式 @EventListener定义监听器

/**
 * 注解方式 @EventListener
 */
@Service
@Slf4j
public class CouponService {
   
   
    /**
     * 监听用户注册事件,执行发放优惠券逻辑
     */
    @EventListener
    public void addCoupon(UserRegisterEvent event) {
   
   
        log.info("给用户[{}]发放优惠券", event.getUsername());
    }
}

实现ApplicationListener的方式定义监听器

/**
 * 实现ApplicationListener<Event>的方式
 */
@Service
@Slf4j
public class EmailService implements ApplicationListener<UserRegisterEvent> {
   
   
    /**
     * 监听用户注册事件, 异步发送执行发送邮件逻辑
     */
    @Override
    @Async
    public void onApplicationEvent(UserRegisterEvent event) {
   
   
        log.info("给用户[{}]发送邮件", event.getUsername());
    }
}

注册事件发布者

@Service
@Slf4j
public class UserService implements ApplicationEventPublisherAware {
   
   

    // 注入事件发布者
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
   
   
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * 发布事件
     */
    public void register(String username) {
   
   
        log.info("执行用户[{}]的注册逻辑", username);
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

定义接口

@RestController
@RequestMapping("/event")
public class UserEventController {
   
   

    @Autowired
    private UserService userService;

    @GetMapping("/register")
    public String register(String username){
   
   
        userService.register(username);
        return "恭喜注册成功!";
    }
}

主程序类

@EnableAsync // 开启异步
@SpringBootApplication
public class SpringBootEventListenerApplication {
   
   

    public static void main(String[] args) {
   
   

        SpringApplication.run(SpringBootEventListenerApplication.class, args);
    }

}

测试接口

启动程序,访问接口:http://localhost:8081/event/register?username=Java,结果如下:

2020-12-21 00:59:46.679  INFO 12800 --- [nio-8081-exec-1] com.hyh.service.UserService              : 执行用户[Java]的注册逻辑
2020-12-21 00:59:46.681  INFO 12800 --- [nio-8081-exec-1] com.hyh.service.CouponService            : 给用户[Java]发放优惠券
2020-12-21 00:59:46.689  INFO 12800 --- [task-1] com.hyh.service.EmailService             : 给用户[Java]发送邮件
目录
相关文章
|
1月前
|
Web App开发 编解码 Java
B/S基层卫生健康云HIS医院管理系统源码 SaaS模式 、Springboot框架
基层卫生健康云HIS系统采用云端SaaS服务的方式提供,使用用户通过浏览器即能访问,无需关注系统的部署、维护、升级等问题,系统充分考虑了模板化、配置化、智能化、扩展化等设计方法,覆盖了基层医疗机构的主要工作流程,能够与监管系统有序对接,并能满足未来系统扩展的需要。
74 4
|
1月前
|
JavaScript 前端开发 Java
基于SpringBoot+Vue实现前后端交互功能(详解Vue框架机制)
基于SpringBoot+Vue实现前后端交互功能(详解Vue框架机制)
|
1月前
|
Java 开发者 UED
Spring Boot的全局异常处理机制
【2月更文挑战第13天】
104 0
|
1月前
|
人工智能 JSON 前端开发
【Spring boot实战】Springboot+对话ai模型整体框架+高并发线程机制处理优化+提示词工程效果展示(按照框架自己修改可对接市面上百分之99的模型)
【Spring boot实战】Springboot+对话ai模型整体框架+高并发线程机制处理优化+提示词工程效果展示(按照框架自己修改可对接市面上百分之99的模型)
|
21天前
|
JavaScript Java 测试技术
微信小程序基于BS模式的学生实习与就业管理springboot+vue.js附带文章和源代码设计说明文档ppt
微信小程序基于BS模式的学生实习与就业管理springboot+vue.js附带文章和源代码设计说明文档ppt
27 1
|
1月前
|
XML Java 开发者
springboot 启动原理、启动过程、启动机制的介绍
【5月更文挑战第13天】Spring Boot 是一种基于 Java 的框架,用于创建独立的、生产级别的 Spring 应用程序。它的主要目标是简化 Spring 应用的初始搭建和开发过程,同时提供一系列大型项目常见的非功能性特征(如嵌入式服务器、安全性、度量、健康检查和外部化配置)。
37 3
|
1月前
|
NoSQL Java Redis
springboot之RedisTemplate的访问单机,哨兵,集群模式
以上是配置RedisTemplate以连接到单机、哨兵和集群模式的示例。在实际应用中,还可以根据需求配置连接池、序列化方式、超时等其他参数。
55 0
|
1月前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
125 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
1月前
|
Java 数据处理 开发者
谈谈springboot的观察者模式
【4月更文挑战第14天】在 Spring Boot 中,观察者模式主要体现在事件驱动架构中,其中事件发布者(主题)向所有注册的事件监听器(观察者)广播事件。这种模式在 Spring 框架中通过使用事件和监听器来实现,适用于多种业务场景,以降低组件之间的耦合度和增强应用的可维护性
73 4
|
1月前
|
NoSQL Java Redis
SpringBoot集成Redis并使用Redis发布订阅模式
SpringBoot集成Redis并使用Redis发布订阅模式
86 3