标题观察者模式在项目中的应用
观察者模式在 Java 语言中的地位非常重要。在 JDK 的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了 JDK 对观察者模式的支持。但是,在 Java9 被弃用了。 另外我们在JAVA应用中都会大量使用spring框架,在spring中也提供了观察者模式的实现。Spring 事件驱动模型也是观察者模式很经典的应用。就是我们常见的项目中最常见的事件监听器。
标题理论说明
先描述一下spring中实现观察者模式所定义的几个角色:
ApplicationEvent(事件),这个类是所有事件对象的父类。
ApplicationEvent 继承自 jdk 的 EventObject, 所有的事件都需要继承 ApplicationEvent, 并且通过 source 得到事件源。大家可以深挖一下spring,它给我们提供了很多内置事件供我们去扩展,比如
ContextStartedEvent:Spring Context 启动完成事件。
ContextStoppedEvent:Spring Context 停止完成事件。
ContextClosedEvent:Spring Context 停止开始事件。
ContextRefreshedEvent:Spring Context 初始化或刷新完成事件。
ApplicationListener(监听),也就是观察者,继承自 jdk 的 EventListener,该类中只有一个方法 onApplicationEvent。当监听的事件发生后该方法会被执行。
ApplicationContext(源),ApplicationContext 是 Spring 中的核心容器,在事件监听中 ApplicationContext 可以作为事件的发布者,也就是事件源。因为 ApplicationContext 继承自 ApplicationEventPublisher。在 ApplicationEventPublisher 中定义了事件发布的方法:publishEvent(Object event)
ApplicationEventMulticaster(事件管理者),用于事件监听器的注册和事件的广播。监听器的注册就是通过它来实现的,它的作用是把 Applicationcontext 发布的 Event 广播给它的监听器列表。
场景下对比
我们举个业务场景,推送发布任务的时候到业务层之后需要注入另外3个业务层的控制类,之后进行对应的业务处理,就如下图,可以看出这个业务之间的耦合就很严重。
基于上边的流程我们使用观察者模式改造一下:松耦合
观察者模式对我们很大的一个作用,在于实现业务的解耦。来个简单的场景,以用户注册的场景来举例子,假设在用户注册完成时,需要给该用户发送邮件、发送优惠劵等等操作,如下图所示:
那么我们就基于这个注册发消息这个场景来举个例子,上代码:
- 我们构建一个springboot项目,引入依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-54-demo</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
- 定义一个事件(此例就是用户注册事件)
package com.springboot.original.service;
import org.springframework.context.ApplicationEvent;
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;
}
}
- 定义一个具体的业务实现类UserService
@Service
public class UserService implements ApplicationEventPublisherAware {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserMapper userMapper;
@Resource
RedisUtil redisUtil;
private ApplicationEventPublisher applicationEventPublisher;
public User selectByPrimaryKey(String id){
return userMapper.selectByPrimaryKey(id);
}
public int insertUser(User user){
return userMapper.insert(user);
}
public User selectByNickName(String name){
redisUtil.set("aaa","bbb");
return userMapper.selectByNickName(name);
}
public String getFromRedis(String key){
return redisUtil.get(key)+"";
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void register(String username) {
//注册逻辑
System.out.println("[register][执行用户({}) 的注册逻辑] "+ username);
//发布消息
applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
}
}
- 定义EmailService用于监听发布消息后进行发送邮件的业务处理
package com.springboot.original.service;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {
@Override
@Async
public void onApplicationEvent(UserRegisterEvent event) {
System.out.println("[onApplicationEvent][给用户({}) 发送邮件]"+ event.getUsername());
}
}
说明一下该类的作用,这个类实现 ApplicationListener 接口,通过泛型设置感兴趣的事件。此处设置为UserRegisterEvent这个事件。之后针对监听的UserRegisterEvent 事件,进行自定义处理。
- 我们再定义个优惠劵 Service
package com.springboot.original.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
@Service
public class CouponService {
private Logger logger = LoggerFactory.getLogger(getClass());
@EventListener
public void addCoupon(UserRegisterEvent event) {
logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
}
}
注意添加 该类中方法添加了@EventListener 注解,其实这个是另一种写法而已,其本质是跟发送邮件的service含义是一样的,通过增加注解标识该方法正在监听事件为 UserRegisterEvent的事件。
- 我们测试一下从controller调用正常的注册方法,会触发对应的邮件监听事件和优惠券监听事件
各个监听顺序控制
如果有多个业务实现类进行监听同一个事件并且需要在不同的业务实现类进行顺序控制那么我们就有两种情况:
1、采用注解的方式进行监听,那么order注解就要配置在标记监听注解的方法上
2、采用实现类实现其方法进行监听时,那么order注解就要配置在业务实现类上
@Order注解值越小越先加载
观察者模式与发布订阅模式
对于观察者模式,我们仅仅维护一个可观察者对象即可,即一个 Observable 实例,当有数据变更时,它只需维护一套观察者(Observer)的集合,这些 Observer 实现相同的接口,Subject 只需要知道,通知 Observer 时,需要调用哪个统一方法就好了。如下图:
对于发布订阅模式更像一个MQ中间件
与观察者模式不一样,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。观察者模式里,只有两个角色 —— 观察者 + 被观察者,而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个管理并执行消息队列的“中间者”。