55-字典缓存与通知SPI

55-字典缓存与通知SPI

两个小模块合成一篇:dict的computeIfAbsent缓存与notify的META-INF/services渠道注册------都是百行级代码,但各自代表一种经典模式。

文章目录

源码:

browise-dict/src/main/java/com/browise/dict/service/DictService.java(2.8KB)

browise-notify/src/main/java/com/browise/notify/channel/NotifyChannelManager.java(1.4KB)


一、DictService:Cache-Aside的教科书实现

java 复制代码
@Service
public class DictService {

    private final ConcurrentHashMap<String, List<DictItem>> cache = new ConcurrentHashMap<>();
    private final Aa10Mapper aa10Mapper;

    public List<DictItem> getItems(String codeName) {
        // ①缓存命中直接返回
        List<DictItem> items = cache.get(codeName);
        if (items != null) return items;

        // ②miss→查库→入缓存
        items = aa10Mapper.selectByAaa100(codeName);
        cache.put(codeName, items);
        return items;
    }

    public String getLabel(String codeName, String value) {
        // 码值翻译:"1"→"男"
        return getItems(codeName).stream()
            .filter(i -> i.getValue().equals(value))
            .map(DictItem::getLabel).findFirst().orElse(value);
    }

    public void refresh(String codeName) {
        cache.remove(codeName);        // 失效------下次get重查
        // 或全量:cache.clear()
    }

    public void saveItem(DictItem item) {
        aa10Mapper.insert(item);
        refresh(item.getAaa100());      // 写后失效------自己先看见
    }
}

Cache-Aside三件套 ------读(miss回源)、写(先库后失效缓存)、强制失效(refresh)。computeIfAbsent的单行版也可以(第01篇提过browise-dict用它)------原子性更好但锁粒度大(同key并发查库只放一个进去)------两种写法在这个低频场景都成立。

为什么字典适合缓存 ------AA10码表读极频 (每个下拉框/每列表格的翻译)写极少(管理员偶尔加码值)------读多写少是缓存的第一判据。

不可变列表的细节 ------查询结果直接缓存引用------调用方拿到List如果add会污染缓存 。生产版该Collections.unmodifiableList()包一层------当前实现靠约定(没人去改字典列表)。


二、Aa10实体的两个特殊处理

第58篇bug记录里两条都关于Aa10:

java 复制代码
@TableName("AA10")
public class Aa10 {
    // 没有@TableId!------复合主键(AAA100+AAA102)MP不支持
    // → updateById/deleteById不可用
    // → 自定义@Update("UPDATE AA10 SET AAA103=#{label} WHERE AAA100=#{type} AND AAA102=#{code}")
}

无@TableId的实体 ------MP的BaseMapper大量方法依赖单主键------复合主键表只能手写SQL 。这是MyBatis-Plus的边界------政务标准表(劳动部99版数据字典)复合主键很常见------BaseEntity路径不够走原生Mapper

DELETE的body问题 ------HTTP DELETE带body不符合部分前端/网关规范------改成@RequestParam query paramDELETE /api/codes/item?aaa100=x&aaa102=y)------第58篇的三条修复之一。


三、NotifyChannelManager:Java SPI

java 复制代码
public class NotifyChannelManager {
    private final Map<String, NotifyChannel> channels = new ConcurrentHashMap<>();

    public NotifyChannelManager() {
        // ServiceLoader扫描META-INF/services
        ServiceLoader<NotifyChannel> loader = ServiceLoader.load(NotifyChannel.class);
        for (NotifyChannel ch : loader) {
            channels.put(ch.getId(), ch);
        }
    }

    public void send(String channelId, String to, String msg) {
        NotifyChannel ch = channels.get(channelId);
        if (ch != null) ch.send(to, msg);
    }
}

META-INF/services注册 ------每个渠道jar的META-INF/services/com.browise.notify.channel.NotifyChannel文件写实现类全名------ServiceLoader运行时发现

与第39篇拦截器的Spring版对照

拦截器链(Spring getBeansOfType) 通知渠道(Java SPI)
注册方式 @Component自动 META-INF/services文件
依赖 Spring容器 无------纯JDK
适用 应用内扩展 跨jar/独立部署

notify为什么选Java SPI ------通知渠道可能是独立jar (阿里云SMS SDK封装)------不依赖Spring的模块更轻------demo应用引jar即得渠道,不引就没有。


四、两个渠道实现

WorkNoticeChannel(5.6KB)------政务协同办公平台的HTTP通知------纯POST。

AliyunSmsChannel (5.8KB)------短信------HMAC-SHA1签名 + 降级策略

复制代码
发短信
  → 签名计算
  → HTTP调阿里云
  → 失败 → 降级:写本地待发表 → 定时任务重试

降级的存在理由 ------短信网关的可用性不能绑架业务------通知发不出不该阻断主流程(与第43篇推送失败的尽力而为同理------通知是增强不是依赖)。


五、字典+通知在待办提醒里的协作

复制代码
任务逾期(ActPrcday配置到期------第42篇)
  → 定时任务扫描
  → NotifyService.send("workNotice", 处理人, "您有待办逾期")
     → NotifyChannelManager路由到WorkNoticeChannel
        → HTTP推政务平台
  → 前端消息中心同时可见(WORKFLOW_MESSAGE表)

字典服务给所有展示层翻译码值、通知服务把事件送达渠道------两个"周边"模块托着所有业务模块的公共需求


✅ 亮点:Cache-Aside三件套与不可变列表细节、复合主键表的MP边界、SPI与Spring注册的跨jar适用性对照、短信渠道的降级策略、字典+通知在逾期提醒的协作链。适合做公共组件的人。扩展方向:第39篇Spring注册对照、第42篇时限配置、第58篇bug修复。

相关推荐
captain3761 小时前
HTTP(2)
java·网络·http·java-ee
徐子童1 小时前
介绍MVCC机制
java·mysql·面试题·秋招·并发·mvcc
Wang's Blog1 小时前
Java 项目实战: 外卖平台-启用禁用员工账号与Long精度丢失修复
java·服务器
乌暮1 小时前
Java 抽象类与接口详解:半成品图纸 vs 能力合同
java·开发语言·后端·学习
一条泥憨鱼1 小时前
苍穹外卖【day12| 工作台,通过Apache POI导入/导出运营数据】
java·后端·苍穹外卖
心平气和量大福大1 小时前
android-权限
android·java
006_1 小时前
ruoyi-vue-plus项目 minio外网访问配置
java
天空属于哈夫克31 小时前
企业微信二次开发:用 sendText 做外部群工单播报
架构·企业微信
MarkZhou6241 小时前
智能蒸烤双核通信架构拆解:从冗余耦合到分层解耦
stm32·单片机·嵌入式硬件·架构·安卓·安全架构