本章给大家分享下,观察者模式在项目中是怎样玩的
01 观察者模式简介
观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者有四个成员对象
Subject(被观察者):又称为主题,是被观察对象,维护了一个观察者列表,定义了注册、移除和通知观察者的方法。当自身状态变化时会主动调用通知方法,触发所有观察者的更新。Observer(观察者):依赖于被观察的对象,定义了更新方法,当主题状态改变时被调用。ConcreteSubject(具体被观察者):观察者的具体实现类,当状态变化时调用通知方法。ConcreteObserver(具体观察者):观察者的具体实现类,根据被观察者的通知执行具体操作。
02 基础代码实现
以用户注册为例,当注册成功后,记录注册日志、给用户发送短信消息、给用户发放优惠券:
用户注册实体
java
@Data
public class User {
private static int nextId = 1;
private int id;
private String username;
private String phone;
public User(String username, String phone) {
this.id = nextId++;
this.username = username;
this.phone = phone;
}
}
观察者接口
java
/**
* 注册观察者接口
*/
public interface RegistrationObserver {
void onUserRegistered(User user);
}
观察者的不同实现类
java
/**
* 优惠券发放观察者
*/
public class CouponDistributor implements RegistrationObserver {
@Override
public void onUserRegistered(User user) {
System.out.println("[优惠券服务] 向用户 " + user.getUsername() +
" 发放新用户优惠券(代金券10元)");
// 这里实际应该调用优惠券系统API
}
}
java
/**
* 注册日志观察者
*/
public class RegistrationLogger implements RegistrationObserver {
@Override
public void onUserRegistered(User user) {
System.out.println("[日志服务] 记录用户注册日志 - 用户: " + user.getUsername() +
", 时间: " + java.time.LocalDateTime.now());
// 这里实际应该写入日志文件或数据库
}
}
java
/**
* 短信通知观察者
*/
public class SmsNotifier implements RegistrationObserver {
@Override
public void onUserRegistered(User user) {
System.out.println("[短信服务] 向 " + user.getPhone() + " 发送欢迎短信: " +
"尊敬的用户 " + user.getUsername() +
",欢迎注册我们的服务!");
// 这里实际应该调用短信API
}
}
注册业务类(被观察者)
java
/**
* 用户注册服务(被观察者)
*/
public class UserRegistrationService {
/**
* 使用list集合存储观察者
*/
private List<RegistrationObserver> observers = new ArrayList<>();
/**
* 注册观察者
*/
public void addObserver(RegistrationObserver observer) {
observers.add(observer);
}
/**
* 移除观察者
*/
public void removeObserver(RegistrationObserver observer) {
observers.remove(observer);
}
/**
* 核心注册方法
*/
public void registerUser(String username, String phone) {
// 1. 执行核心注册业务逻辑(模拟)
User newUser = new User(username, phone);
// todo 这里应该有实际的用户保存到数据库等操作...
System.out.println("用户注册成功: " + newUser);
// 2. 注册成功后通知所有观察者
notifyObservers(newUser);
}
/**
* 通知所有观察者用户注册成功
*/
private void notifyObservers(User user) {
for (RegistrationObserver observer : observers) {
observer.onUserRegistered(user);
}
}
}
测试demo入口
java
/**
* 测试类
*/
public class UserRegistrationDemo {
public static void main(String[] args) {
// 1. 创建注册服务(被观察者)
UserRegistrationService registrationService = new UserRegistrationService();
// 2. 创建各种观察者并进行注册
registrationService.addObserver(new RegistrationLogger());
registrationService.addObserver(new SmsNotifier());
registrationService.addObserver(new CouponDistributor());
// 3. 执行用户注册(将触发所有观察者)
registrationService.registerUser("张三", "18600000000");
registrationService.registerUser("李四", "19500000000");
}
}
输出结果
shell
用户注册成功: User(id=1, username=张三, phone=18600000000)
[日志服务] 记录用户注册日志 - 用户: 张三, 时间: 2025-11-03T16:15:36.398190200
[短信服务] 向 18600000000 发送欢迎短信: 尊敬的用户 张三,欢迎注册我们的服务!
[优惠券服务] 向用户 张三 发放新用户优惠券(代金券10元)
用户注册成功: User(id=2, username=李四, phone=19500000000)
[日志服务] 记录用户注册日志 - 用户: 李四, 时间: 2025-11-03T16:15:36.402189600
[短信服务] 向 19500000000 发送欢迎短信: 尊敬的用户 李四,欢迎注册我们的服务!
[优惠券服务] 向用户 李四 发放新用户优惠券(代金券10元)
可以看到每当用户注册成功后,都会触发观察者的业务逻辑处理,通过此种模式让我们的业务处理更直观、耦合度更低。
03 项目实战
以上是观察者模式的简化版,和真实业务中的使用还是有些区别的。接下来我们看下在项目(springboot架构)中的观察者模式(一般是通过事件的发布订阅实现)。
首先将观察者交由spring容器管理
以下是上面代码的改造: 新建一个事件类,里面包含了User信息:
java
/**
* @description 用户注册事件
*/
@Getter
public class UserRegistrationEvent extends ApplicationEvent {
/**
* 注册成功的用户
*/
private final User user;
public UserRegistrationEvent(Object source, User user) {
super(source);
this.user = user;
}
}
通过spring的ApplicationEventPublisher类进行用户注册后的事件发布:
java
/**
* 用户注册服务(被观察者)
*/
@Slf4j
@Service
public class UserRegistrationService {
@Resource
private ApplicationEventPublisher eventPublisher;
/**
* 核心注册方法
*/
@Transactional(rollbackFor = Exception.class)
public void registerUser(String username, String phone) {
// 1. 执行核心注册业务逻辑(模拟)
User newUser = new User(username, phone);
log.info("用户注册成功,保存到数据库中: {}", newUser);
// 2. 注册成功后通知所有观察者
eventPublisher.publishEvent(new UserRegistrationEvent(this, newUser));
}
}
通过监听event事件处理不同的业务:
java
/**
* @description 用户注册监听业务
*/
@Slf4j
@Component
public class UserRegistrationListener {
@EventListener(value = UserRegistrationEvent.class)
public void logOnRegistration(UserRegistrationEvent event) {
User user = event.getUser();
log.info("[日志服务] 记录用户注册日志 - 用户: {}, 时间: {}", user.getUsername(), java.time.LocalDateTime.now());
// 这里实际应该写入日志文件或数据库
}
@EventListener(value = UserRegistrationEvent.class)
public void smsOnRegistration(UserRegistrationEvent event) {
User user = event.getUser();
log.info("[短信服务] 向 {} 发送欢迎短信: {}", user.getPhone(),
"尊敬的用户 " + user.getUsername() +
",欢迎注册我们的服务!");
// 这里实际应该调用短信API
}
@EventListener(value = UserRegistrationEvent.class)
public void couponOnRegistration(UserRegistrationEvent event) {
User user = event.getUser();
log.info("[优惠券服务] 向用户 {} 发放新用户优惠券(代金券10元)", user.getUsername());
// 这里实际应该调用优惠券系统API
}
}
以上我们的改造完成了,接下来写一个简单的测试类测试下吧:
java
/**
* @description 测试观察者模式
*/
@RestController
@RequestMapping("/observer")
public class TestObserverController {
@Resource
private UserRegistrationService userRegistrationService;
@GetMapping("/test")
public void register(String name, String phone) {
userRegistrationService.registerUser(name, phone);
}
}
看下输出结果:
shell
用户注册成功,保存到数据库中: User(id=1, username=aaaa, phone=13000000000)
[日志服务] 记录用户注册日志 - 用户: aaaa, 时间: 2025-11-03T16:52:53.020144200
[短信服务] 向 13000000000 发送欢迎短信: 尊敬的用户 aaaa,欢迎注册我们的服务!
[优惠券服务] 向用户 aaaa 发放新用户优惠券(代金券10元)
如果后续有业务扩展的话,只需要再实现一个监听的方法即可。
好了 ,看到这里是不是觉得大功告成了(哇偶,又学会了一项新技能😀😀😀),其实真实的业务中,比我们实现的要复杂很多,还有不少需要注意的地方,我在这里也和大家简单聊一下。
处理顺序
比如产品又出新需求了,用户注册成功后的短信内容,必须包含发放的优惠券信息,这就要求我们做好事件触发顺序,先发放优惠券,再去发放短信,这里就用到了spring的另一个注解@Order,添加到监听方法中,值越小,执行越靠前,代码改造后如下所示(执行顺序为日志 > 优惠券 > 短信):
java
/**
* @description 用户注册监听业务
*/
@Slf4j
@Component
public class UserRegistrationListener {
@Order(1)
@EventListener(value = UserRegistrationEvent.class)
public void logOnRegistration(UserRegistrationEvent event) {
User user = event.getUser();
log.info("[日志服务] 记录用户注册日志 - 用户: {}, 时间: {}", user.getUsername(), java.time.LocalDateTime.now());
// 这里实际应该写入日志文件或数据库
}
@Order(3)
@EventListener(value = UserRegistrationEvent.class)
public void smsOnRegistration(UserRegistrationEvent event) {
User user = event.getUser();
// 查询优惠券信息
String couponInfo = "新用户优惠券(代金券10元)";
log.info("[短信服务] 向 {} 发送欢迎短信: {}, 并赠送您{}", user.getPhone(),
"尊敬的用户 " + user.getUsername() +
",欢迎注册我们的服务!", couponInfo);
// 这里实际应该调用短信API
}
@Order(2)
@EventListener(value = UserRegistrationEvent.class)
public void couponOnRegistration(UserRegistrationEvent event) {
User user = event.getUser();
log.info("[优惠券服务] 向用户 {} 发放新用户优惠券(代金券10元)", user.getUsername());
// 这里实际应该调用优惠券系统API
}
}
处理超时
比如写日志时,日志服务响应延迟,导致写入超时、业务回滚,用户注册失败,这就要求我们做好异步处理,简单点的做法是使用spring的另一个注解 @Async,此时会开启一个线程单独执行该方法,主线程继续执行后续逻辑。首先需要在项目启动类上添加@EnableAsync注解,其次在想要实现异步的监听方法上添加@Async:
java
@Async
@Order(3)
@EventListener(value = UserRegistrationEvent.class)
public void logOnRegistration(UserRegistrationEvent event) {
User user = event.getUser();
log.info("[日志服务] 记录用户注册日志 - 用户: {}, 时间: {}", user.getUsername(), java.time.LocalDateTime.now());
// 这里实际应该写入日志文件或数据库
}
流量削峰/业务重试机制
当接口请求并发高时或者为了提示业务的容错性时,不再使用spring事件发布处理机制,采用mq的监听是一种更好的实现方式:
java
/**
* 用户注册服务(被观察者)
*/
@Slf4j
@Service
public class UserRegistrationMqService {
@Resource
private RocketMQTemplate rocketMQTemplate;
/**
* 核心注册方法
*/
@Transactional(rollbackFor = Exception.class)
public void registerUser(String username, String phone) {
// 1. 执行核心注册业务逻辑(模拟)
User newUser = new User(username, phone);
log.info("用户注册成功,保存到数据库中: {}", newUser);
// 2. 通过mq通知所有观察者业务
rocketMQTemplate.convertAndSend("user_registration_topic", JSON.toJSONString(newUser));
}
}
java
/**
* @description 短信监听
*/
@Slf4j
@Component
@RocketMQMessageListener(
consumerGroup = "SmsGroup",
topic = "user_registration_topic"
)
public class SmsMqListener implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
User user = JSON.parseObject(message, User.class);
log.info("短信监听,用户注册成功,用户信息: {}", user);
}
}
好了,我们这次的分享就到这里,如果对你有帮助的话,那就点赞收藏一下吧 😀😀😀😀