原来项目中的观察者模式是这样玩的

本章给大家分享下,观察者模式在项目中是怎样玩的

01 观察者模式简介

观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

观察者有四个成员对象

  1. Subject(被观察者):又称为主题,是被观察对象,维护了一个观察者列表,定义了注册、移除和通知观察者的方法。当自身状态变化时会主动调用通知方法,触发所有观察者的更新。
  2. Observer(观察者):依赖于被观察的对象,定义了更新方法,当主题状态改变时被调用。
  3. ConcreteSubject(具体被观察者):观察者的具体实现类,当状态变化时调用通知方法。
  4. 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);
    }
}

好了,我们这次的分享就到这里,如果对你有帮助的话,那就点赞收藏一下吧 😀😀😀😀

相关推荐
喝杯绿茶6 小时前
springboot中的事务
java·spring boot·后端
麦兜*6 小时前
多阶段构建:打造最小化的 Spring Boot Docker 镜像
java·spring boot·后端·spring cloud·docker
RustCoder6 小时前
Rust 1.91 发布:ARM Windows 正式跻身顶级支持,悬空指针警告上线
后端·性能优化·rust
用户8356290780516 小时前
Python创建PDF文档:解锁高效PDF创建的能力
后端·python
golang学习记6 小时前
FastAPI + SQLModel 实战:一个模型搞定数据库与 API
后端
oak隔壁找我6 小时前
Spring Boot Starter 入门教程
java·后端
Data_Adventure7 小时前
Java 与 TypeScript 的核心对比
前端·后端
Data_Adventure7 小时前
从前端到 Java 后端:一份详细转型路线指南
前端·后端
绝无仅有7 小时前
某游戏大厂计算机网络面试问题深度解析(一)
后端·面试·架构