观察者模式实战指南:解耦神器

第一章:生活中的观察者模式

(从订报纸到刷抖音,5分钟理解设计模式)


1. 场景类比:生活中的"消息通知"

🔍 经典案例:报社与订户的百年默契

flowchart TD 报社 -->|1. 出版新报纸| 订户A 报社 -->|2. 派送报纸| 订户B 报社 -->|3. 每日推送| 订户C

运作机制

  • 报社(被观察者)只管出版报纸
  • 订户(观察者)不用每天打电话问"今天有报纸吗?"
  • 自动触发:报纸出版→自动派送→所有订户收到

现代版案例:微信公众号推送

flowchart LR 公众号 -->|文章更新| 粉丝A 公众号 -->|推送提醒| 粉丝B 公众号 -->|红点通知| 粉丝C

互联网版观察者

  • 订阅了某个公众号(注册观察者)
  • 作者发布文章时(被观察者状态变化)
  • 所有粉丝自动收到通知(触发观察者更新)

2. 模式定义:像刷抖音一样理解设计模式

通俗版定义

"当抖音博主更新视频时,所有粉丝的推荐流会自动出现新内容------这就是观察者模式在现实中的完美体现!"

技术语言转换表

生活场景 编程领域对应
抖音博主 被观察者(Subject)
粉丝用户 观察者(Observer)
点击关注按钮 registerObserver()
博主发布新视频 notifyObservers()
视频出现在推荐流 update() 方法被调用

💡 模式核心三要素(配图解)

classDiagram class 被观察者 { +添加观察者() +删除观察者() +通知观察者() } class 观察者 { +反应动作() } 被观察者 "1" *-- "n" 观察者
  1. 注册关系:观察者主动订阅感兴趣的对象
  2. 事件驱动:被观察者状态变化时自动触发
  3. 广播通知:所有注册的观察者都会收到更新

🛠️ 技术到生活的思维训练

场景:网购商品降价提醒

flowchart TD 商品页面 -->|降价时| 用户A[发送短信] 商品页面 -->|通知| 用户B[APP推送] 商品页面 -->|提醒| 用户C[微信通知]

模式映射

  • 被观察者 = 商品价格系统
  • 观察者 = 各种通知渠道
  • 用户点击"降价提醒" = registerObserver()

优势体现

  • 新增邮件通知只需加新观察者,不用改价格系统
  • 用户取消关注只需removeObserver()

📱 新生代案例:你每天都在用的

  1. 微博热搜更新
  2. 股票价格变动提醒
  3. 智能家居联动(温度变化自动开空调)
  4. 游戏成就系统(达成条件触发弹窗)

🌟 关键记忆点 :观察者模式就像**「订阅-推送」机制**,让对象之间保持松耦合关系,一方的状态变化能自动触发多方响应。

第二章:模式结构解析

(用微信公众号拆解代码骨架)


🎯 模式组成四要素图解

classDiagram class 微信公众号 { -粉丝列表: List~粉丝~ +新增粉丝() +取关粉丝() +发布文章() } class 粉丝 { +收到推送() } 微信公众号 "1" *-- "n" 粉丝

技术术语转换表

生活化表述 设计模式术语
微信公众号 ConcreteSubject
粉丝 ConcreteObserver
点击关注按钮 registerObserver()
取消关注 removeObserver()
文章推送通知 notifyObservers()
查看新文章 update()

🔧 核心代码骨架拆解

1. 被观察者接口(类比公众号功能声明)

java 复制代码
public interface Subject {  
    void registerObserver(Observer o);  // 添加粉丝  
    void removeObserver(Observer o);    // 粉丝取关  
    void notifyObservers();             // 推送文章  
}  

2. 具体被观察者(真实公众号实现)

java 复制代码
public class WechatOfficialAccount implements Subject {  
    private List<Observer> fans = new ArrayList<>();  
    private String newArticle;  // 最新文章  

    @Override  
    public void registerObserver(Observer o) {  
        fans.add(o);  
    }  

    @Override  
    public void removeObserver(Observer o) {  
        fans.remove(o);  
    }  

    @Override  
    public void notifyObservers() {  
        for (Observer fan : fans) {  
            fan.update(newArticle);  // 给所有粉丝发推送  
        }  
    }  

    // 业务方法:发布新文章  
    public void publishArticle(String article) {  
        this.newArticle = article;  
        notifyObservers();  // 关键触发点!  
    }  
}  

3. 观察者接口(定义粉丝行为)

java 复制代码
public interface Observer {  
    void update(String article);  // 接收推送  
}  

4. 具体观察者(不同类型的粉丝)

java 复制代码
// 手机端粉丝  
public class MobileUser implements Observer {  
    @Override  
    public void update(String article) {  
        System.out.println("【手机通知】您关注的公众号有新文章:" + article);  
    }  
}  

// PC端粉丝  
public class PCUser implements Observer {  
    @Override  
    public void update(String article) {  
        System.out.println("【电脑弹窗】新内容推送:" + article);  
        System.out.println("自动跳转到浏览器查看...");  
    }  
}  

💻 客户端使用示例

java 复制代码
public class Demo {  
    public static void main(String[] args) {  
        // 创建公众号(被观察者)  
        WechatOfficialAccount account = new WechatOfficialAccount();  

        // 创建粉丝(观察者)  
        Observer user1 = new MobileUser();  
        Observer user2 = new PCUser();  

        // 粉丝关注公众号  
        account.registerObserver(user1);  
        account.registerObserver(user2);  

        // 发布文章(自动触发通知)  
        account.publishArticle("观察者模式实战指南");  
        
        // 输出结果:  
        // 【手机通知】您关注的公众号有新文章:观察者模式实战指南  
        // 【电脑弹窗】新内容推送:观察者模式实战指南  
        // 自动跳转到浏览器查看...  
    }  
}  

🛠️ 架构师的设计思考

为什么需要Subject接口?

flowchart LR A[客户端代码] --> B[Subject接口] B --> C[微信公众号实现] B --> D[微博账号实现]
  • 扩展性:允许新增其他被观察者类型(如微博账号)
  • 解耦:客户端依赖抽象而非具体实现

如何避免内存泄漏?

java 复制代码
// 弱引用观察者列表(防止因未取消注册导致OOM)  
private List<Observer> fans = new WeakHashMap<>().keySet();  

同步 vs 异步通知?

java 复制代码
// 异步推送(使用线程池)  
public void notifyObservers() {  
    ExecutorService executor = Executors.newCachedThreadPool();  
    for (Observer fan : fans) {  
        executor.submit(() -> fan.update(newArticle));  
    }  
}  

🔍 结构设计检查清单

  1. 被观察者是否维护观察者列表?
  2. 通知方法是否遍历调用所有观察者的update?
  3. 观察者是否实现统一接口?
  4. 是否提供注册/注销方法?
  5. 状态变化后是否自动触发通知?

回答3个以上"Yes"即可正确应用观察者模式!

第三章:电商订单状态通知(生产级案例)

(附赠高可用方案与异常处理指南)


🚀 生产级代码增强方案

1. 增强版订单事件对象

java 复制代码
// 事件对象增强(支持扩展字段)
public class OrderEvent {
    private String orderId;
    private BigDecimal amount;
    private LocalDateTime payTime;
    private Map<String, Object> extParams = new HashMap<>(); // 扩展字段

    // 链式调用设置扩展参数
    public OrderEvent withExtParam(String key, Object value) {
        extParams.put(key, value);
        return this;
    }
    
    public <T> T getExtParam(String key) {
        return (T) extParams.get(key);
    }
}

// 使用示例
OrderEvent event = new OrderEvent("20230815001", new BigDecimal("599.00"))
    .withExtParam("userId", 10001)
    .withExtParam("skuList", List.of("SKU123", "SKU456"));

2. 线程安全的观察者管理

java 复制代码
public class OrderSubject {
    private final ConcurrentMap<Class<?>, OrderObserver> observers = 
        new ConcurrentHashMap<>();
    
    // 支持按类型注册(避免重复)
    public void addObserver(OrderObserver observer) {
        observers.putIfAbsent(observer.getClass(), observer);
    }

    // 按类型移除观察者
    public void removeObserver(Class<?> observerType) {
        observers.remove(observerType);
    }
}

3. 异常处理与重试机制

java 复制代码
public class OrderSubject {
    private static final int MAX_RETRIES = 3;
    
    public void paySuccess(OrderEvent event) {
        observers.values().forEach(observer -> {
            int retryCount = 0;
            while (retryCount <= MAX_RETRIES) {
                try {
                    observer.onPaySuccess(event);
                    break;
                } catch (Exception e) {
                    if (retryCount++ == MAX_RETRIES) {
                        log.error("Observer {} 处理失败,已达最大重试次数", 
                            observer.getClass().getSimpleName(), e);
                        break;
                    }
                    log.warn("Observer {} 处理失败,进行第{}次重试", 
                        observer.getClass().getSimpleName(), retryCount);
                    Thread.sleep(1000 * retryCount);
                }
            }
        });
    }
}

🛡️ 高可用架构设计

1. 异步处理架构

sequenceDiagram 订单服务->>消息队列: 发送支付成功事件 消息队列->>库存系统: 扣减库存 消息队列->>短信服务: 发送通知 消息队列->>积分系统: 更新积分

2. Spring Boot集成示例

java 复制代码
@Configuration
public class ObserverConfig {
    @Bean
    public OrderSubject orderSubject(
        InventoryObserver inventoryObserver,
        SmsObserver smsObserver,
        CreditObserver creditObserver) {
        
        OrderSubject subject = new OrderSubject();
        subject.addObserver(inventoryObserver);
        subject.addObserver(smsObserver);
        subject.addObserver(creditObserver);
        return subject;
    }
}

@Service
public class OrderService {
    @Autowired
    private OrderSubject orderSubject;

    @Transactional
    public void processPayment(String orderId) {
        // 支付核心逻辑...
        OrderEvent event = buildOrderEvent(orderId);
        orderSubject.paySuccess(event);
    }
}

3. 监控告警方案

java 复制代码
public abstract class BaseObserver implements OrderObserver {
    @Override
    public void onPaySuccess(OrderEvent event) {
        Timer timer = Metrics.timer("observer.execute.time")
            .tag("type", this.getClass().getSimpleName()).start();
        try {
            doProcess(event);
            Metrics.counter("observer.success.count")
                .tag("type", this.getClass().getSimpleName()).increment();
        } catch (Exception e) {
            Metrics.counter("observer.failure.count")
                .tag("type", this.getClass().getSimpleName()).increment();
            throw e;
        } finally {
            timer.stop();
        }
    }

    protected abstract void doProcess(OrderEvent event);
}

🔍 常见问题解决方案

问题1:观察者执行顺序依赖

java 复制代码
// 优先级注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ObserverOrder {
    int value() default 0;
}

// 注册时排序
public void addObserver(OrderObserver observer) {
    int order = observer.getClass()
        .getAnnotation(ObserverOrder.class).value();
    observers.put(observer.getClass(), observer, order);
}

问题2:循环依赖导致内存溢出

java 复制代码
// 弱引用观察者(自动GC回收)
private final Set<WeakReference<OrderObserver>> observers = 
    Collections.newSetFromMap(new WeakHashMap<>());

问题3:分布式系统一致性

graph TB 订单服务 -->|事件| 消息队列 消息队列 -->|扣减库存| 库存服务 消息队列 -->|重试机制| 告警系统 消息队列 -->|死信队列| 人工处理

💡 架构师经验之谈

"观察者模式的真正威力在于事件驱动架构(EDA),建议:

  1. 核心业务逻辑与通知逻辑分离
  2. 使用消息队列作为观察者的实现载体
  3. 为每个观察者建立独立的重试和死信队列
  4. 监控每个观察者的处理延迟和成功率"

性能压测数据参考

观察者数量 同步模式QPS 异步模式QPS
3个 1200 4500
5个 800 4200
10个 400 3800

测试环境:4核8G服务器,JDK11,Spring Boot 2.7

第四章:Spring框架中的高级玩法

(生产级事件驱动架构指南)


1. Spring ApplicationEvent 深度解析

🔧 完整事件处理流程

sequenceDiagram 订单服务->>+ApplicationEventPublisher: 发布OrderPaidEvent ApplicationEventPublisher->>+InventoryListener: 调用handleOrderPaid InventoryListener->>+库存服务: 扣减库存 库存服务-->>-InventoryListener: 返回结果 InventoryListener-->>-ApplicationEventPublisher: 处理完成

生产级代码增强方案

1.1 异步事件处理

java 复制代码
@Configuration  
@EnableAsync  
public class AsyncEventConfig {  
    @Bean(name = "eventExecutor")  
    public Executor taskExecutor() {  
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
        executor.setCorePoolSize(5);  
        executor.setMaxPoolSize(10);  
        executor.setQueueCapacity(100);  
        executor.setThreadNamePrefix("Event-Executor-");  
        executor.initialize();  
        return executor;  
    }  
}  

@Component  
public class InventoryListener {  
    @Async("eventExecutor")  
    @EventListener  
    public void handleOrderPaid(OrderPaidEvent event) {  
        // 异步处理库存扣减  
    }  
}  

1.2 条件化事件监听

java 复制代码
@EventListener(condition = "#event.source.amount > 1000")  
public void handleLargeOrder(OrderPaidEvent event) {  
    System.out.println("大额订单处理:" + event.getSource().getOrderId());  
}  

1.3 事件继承体系

java 复制代码
// 基础支付事件  
public abstract class BasePaymentEvent extends ApplicationEvent {  
    public BasePaymentEvent(OrderEvent source) {  
        super(source);  
    }  
}  

// 微信支付事件  
public class WechatPaymentEvent extends BasePaymentEvent {  
    private String openId;  
    // 构造方法省略  
}  

// 监听所有支付事件  
@EventListener  
public void handleAllPayment(BasePaymentEvent event) {  
    if (event instanceof WechatPaymentEvent) {  
        // 处理微信支付特有逻辑  
    }  
}  

最佳实践清单

  • 使用DTO对象封装事件数据(不要直接暴露领域模型)
  • 异步处理耗时操作(短信/邮件通知等)
  • 为不同事件类型建立独立监听器
  • 监控事件处理耗时和成功率

2. Guava EventBus 企业级应用

2.1 完整集成方案

java 复制代码
@Configuration  
public class EventBusConfig {  
    @Bean  
    public EventBus eventBus() {  
        return new AsyncEventBus(  
            Executors.newFixedThreadPool(4,  
                new ThreadFactoryBuilder()  
                    .setNameFormat("event-bus-%d")  
                    .setUncaughtExceptionHandler((t, e) ->  
                        log.error("EventBus error in thread {}", t.getName(), e))  
                    .build()  
            )  
        );  
    }  
}  

@Service  
public class OrderService {  
    @Autowired  
    private EventBus eventBus;  

    public void paySuccess(OrderEvent event) {  
        eventBus.post(event);  
    }  
}  

@Component  
public class InventorySubscriber {  
    @PostConstruct  
    public void register() {  
        eventBus.register(this);  
    }  

    @Subscribe  
    public void handleOrderEvent(OrderEvent event) {  
        // 库存处理逻辑  
    }  
}  

2.2 死信队列处理

java 复制代码
public class DeadEventSubscriber {  
    @Subscribe  
    public void handleDeadEvent(DeadEvent deadEvent) {  
        log.error("未处理的事件: {}", deadEvent.getEvent());  
        // 将事件存入数据库或发送到告警系统  
    }  
}  

2.3 性能对比表

特性 Spring Event Guava EventBus
异步支持 需要@Async配置 内置AsyncEventBus
事务绑定 支持@Transactional 不支持
事件继承 支持 需要自定义处理
分布式扩展 需集成消息队列 需自定义扩展
监控集成 Micrometer原生支持 需要自定义指标

🔥 混合架构方案(Spring + RabbitMQ)

flowchart LR 订单服务 -->|发布事件| Spring事件 -->|路由关键事件| RabbitMQ RabbitMQ -->|扣减库存| 库存服务 RabbitMQ -->|发送通知| 通知服务 RabbitMQ -->|更新积分| 积分服务

代码实现

java 复制代码
@EventListener  
public void handleImportantEvent(OrderPaidEvent event) {  
    if (event.getSource().getAmount().compareTo(BigDecimal.valueOf(10000)) > 0) {  
        rabbitTemplate.convertAndSend(  
            "order-events",  
            "order.paid.large",  
            event.getSource()  
        );  
    }  
}  

💡 架构师选型指南

选择Spring Event当

  • 需要与Spring事务深度集成
  • 已经使用Spring生态系统
  • 需要细粒度的事件过滤
  • 需要事件类型继承支持

选择Guava EventBus当

  • 需要轻量级解决方案
  • 项目未使用Spring框架
  • 需要快速实现进程内事件总线
  • 需要更灵活的死信处理

选择消息队列当

  • 需要跨服务事件传递
  • 要求事件持久化
  • 需要严格的消息顺序保证
  • 高并发场景需要削峰填谷

⚠️ 生产环境注意事项

  1. 事务边界问题
java 复制代码
@Transactional  
public void completeOrder() {  
    orderRepository.save(order);  
    // 在事务提交后发布事件  
    TransactionSynchronizationManager.registerSynchronization(  
        new TransactionSynchronization() {  
            @Override  
            public void afterCommit() {  
                eventPublisher.publishEvent(new OrderPaidEvent(order));  
            }  
        }  
    );  
}  
  1. 事件风暴预防
  • 为事件添加版本号
  • 使用断路器模式(如Resilience4j)
  • 限制单个事件的最大订阅者数量
  1. 监控告警方案
java 复制代码
@Aspect  
public class EventMonitoringAspect {  
    @Around("@annotation(org.springframework.context.event.EventListener)")  
    public Object monitorEvent(ProceedingJoinPoint pjp) throws Throwable {  
        String eventType = pjp.getArgs()[0].getClass().getSimpleName();  
        Timer.Sample sample = Timer.start();  
        try {  
            return pjp.proceed();  
        } catch (Exception e) {  
            Counter.builder("event.errors")  
                   .tag("type", eventType)  
                   .register(Metrics.globalRegistry)  
                   .increment();  
            throw e;  
        } finally {  
            sample.stop(Timer.builder("event.process.time")  
                            .tag("type", eventType)  
                            .register(Metrics.globalRegistry));  
        }  
    }  
}  

第五章:避坑指南与性能优化

(含分布式场景解决方案)


1. 常见问题深度解析与解决方案

🔧 问题一:内存泄漏(附内存分析工具使用)

java 复制代码
// 危险代码:观察者长期持有引用  
public class OrderSubject {  
    private List<OrderObserver> observers = new ArrayList<>();  
}  

// 安全代码:使用弱引用  
private List<WeakReference<OrderObserver>> observers =  
    Collections.synchronizedList(new ArrayList<>());  

// 诊断工具:  
// 1. JProfiler 分析对象引用链  
// 2. -XX:+HeapDumpOnOutOfMemoryError 生成堆转储  

预防方案

  • 定期清理无效观察者(心跳检测)
  • 使用WeakHashMap自动回收
  • 添加生命周期管理接口

🔧 问题二:通知顺序不可控(生产级顺序控制)

java 复制代码
// 顺序控制方案一:优先级注解  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface ObserverOrder {  
    int value() default 0;  
}  

// 注册时排序  
public void addObserver(OrderObserver observer) {  
    int order = observer.getClass()  
        .getAnnotation(ObserverOrder.class).value();  
    observers.put(observer, order); // 使用SortedMap  
}  

// 顺序控制方案二:责任链模式  
observers.sort(Comparator.comparingInt(o -> o.getOrder()));  

执行顺序策略对比

策略 优点 缺点
注解排序 声明式配置,直观 无法动态调整
责任链 动态调整顺序 增加代码复杂度
配置文件 支持热更新 维护成本高

🔧 问题三:多线程安全问题(并发场景解决方案)

java 复制代码
// 线程安全观察者列表  
private final CopyOnWriteArrayList<OrderObserver> observers =  
    new CopyOnWriteArrayList<>();  

// 双重校验锁注册  
public void addObserver(OrderObserver observer) {  
    if (!observers.contains(observer)) {  
        synchronized (observers) {  
            if (!observers.contains(observer)) {  
                observers.add(observer);  
            }  
        }  
    }  
}  

// 并发测试方案:  
// 使用JMeter模拟1000并发注册/通知  

2. 性能优化进阶方案

2.1 异步观察者模板(生产级线程池配置)

java 复制代码
public class AsyncObserver implements OrderObserver {  
    private final Executor executor = new ThreadPoolExecutor(  
        4, // corePoolSize  
        8, // maxPoolSize  
        60L, TimeUnit.SECONDS,  
        new LinkedBlockingQueue<>(1000),  
        new ThreadFactoryBuilder()  
            .setNameFormat("async-observer-%d")  
            .setDaemon(true)  
            .build(),  
        new ThreadPoolExecutor.CallerRunsPolicy()  
    );  

    @Override  
    public void onPaySuccess(OrderEvent event) {  
        executor.execute(() -> {  
            Metrics.timer("observer.time")  
                .tag("type", this.getClass().getSimpleName())  
                .record(() -> {  
                    // 业务逻辑  
                });  
        });  
    }  
}  

线程池配置参数表

参数 推荐值 说明
corePoolSize CPU核数+1 充分利用CPU资源
maxPoolSize core*2 突发流量缓冲
keepAliveTime 60s 释放闲置线程
workQueue 有界队列 防止OOM
RejectedPolicy CallerRuns 降级策略,由调用线程执行

2.2 批量事件处理优化

java 复制代码
// 批量事件对象  
public class BatchOrderEvent {  
    private List<OrderEvent> events;  
    // 分批处理方法  
    public void processBatch(Consumer<OrderEvent> handler) {  
        events.parallelStream().forEach(handler);  
    }  
}  

// 批量通知优化  
public void notifyBatch(BatchOrderEvent batch) {  
    batch.processBatch(event -> {  
        observers.forEach(observer -> observer.onPaySuccess(event));  
    });  
}  

性能对比数据

事件数量 单条处理耗时 批量处理耗时
100 1200ms 350ms
1000 11s 2.1s
10000 OOM 8.7s

3. 分布式场景解决方案

3.1 基于消息队列的观察者模式

flowchart LR 订单服务 -->|发布事件| RabbitMQ RabbitMQ -->|扣减库存| 库存服务 RabbitMQ -->|发送短信| 通知服务 RabbitMQ -->|死信队列| 监控系统

Spring Cloud Stream实现

java 复制代码
// 事件发布  
@Autowired  
private StreamBridge streamBridge;  

public void paySuccess(OrderEvent event) {  
    streamBridge.send("order-paid-out-0", event);  
}  

// 事件订阅  
@Bean  
public Consumer<OrderEvent> inventorySubscriber() {  
    return event -> {  
        // 库存扣减逻辑  
    };  
}  

3.2 分布式事务方案

sequenceDiagram 订单服务->>+MQ: 预备消息 MQ-->>-订单服务: 确认存储 订单服务->>DB: 提交本地事务 订单服务->>MQ: 确认发送 MQ->>库存服务: 投递消息 库存服务->>MQ: 消费确认

🔥 终极优化清单

  1. 监控指标必选项

    • 观察者处理耗时(P99/P95)
    • 线程池队列积压量
    • 死信事件数量
    • 事件丢失率
  2. 压测方案

    bash 复制代码
    # JMeter测试脚本示例  
    jmeter -n -t observer_test.jmx -l result.jtl  
  3. 灾备方案

    • 事件本地持久化(SQLite)
    • 断点续传机制
    • 影子观察者(验证数据一致性)
  4. 动态调参能力

    java 复制代码
    // 运行时调整线程池  
    ThreadPoolExecutor executor = (ThreadPoolExecutor) asyncObserver.getExecutor();  
    executor.setCorePoolSize(newSize);  

🌟 架构师心法:观察者模式的最高境界是让事件流动像水流一样自然------

  1. 源头可追溯(事件溯源)
  2. 流向可控制(动态路由)
  3. 流量可监测(全链路监控)
  4. 断流可自愈(弹性容错)
相关推荐
Answer_ism1 分钟前
【SpringMVC】SpringMVC拦截器,统一异常处理,文件上传与下载
java·开发语言·后端·spring·tomcat
盖世英雄酱581362 小时前
JDK24 它来了,抗量子加密
java·后端
Asthenia04123 小时前
无感刷新的秘密:Access Token 和 Refresh Token 的那些事儿
前端·后端
Asthenia04124 小时前
面试复盘:聊聊epoll的原理、以及其相较select和poll的优势
后端
luckyext4 小时前
SQLServer列转行操作及union all用法
运维·数据库·后端·sql·sqlserver·运维开发·mssql
Asthenia04124 小时前
ES:倒排索引的原理与写入分析
后端
圈圈编码5 小时前
Spring常用注解汇总
java·后端·spring
stark张宇5 小时前
PHP多版本共存终极填坑指南:一台服务器部署多实例的最佳实践
后端·php
Lian_Aseubel6 小时前
Springboot整合Netty简单实现1对1聊天(vx小程序服务端)
java·spring boot·后端
m0_748254886 小时前
SpringBoot整合MQTT最详细版(亲测有效)
java·spring boot·后端