ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JCache(JSR-107)在Java EE与Spring Boot中的集成与启用指南

JCache(JSR-107)在Java EE与Spring Boot中的集成与启用指南 面试题刷到这道“Java EE 或 Spring Boot 环境中如何集成和启用 JCache”时我愣了一下。倒不是不会用而是“Java EE 环境”和“Spring Boot 环境”这两个词放在一起正好踩中了很多人的知识盲区大家习惯了在 Spring Boot 里点一个 Cacheable但要解释清楚 JCacheJSR-107到底是什么、它和 Spring Cache 抽象层是什么关系、在 Java EE 容器里怎么玩很多同学就支支吾吾了。这道题出得很有水平属于典型的“会用但讲不清”陷阱。平时项目里缓存用得再多如果底层原理没吃透面试一深挖就会露馅。今天我不打算背标准答案而是把这套东西从规范本身、集成步骤、配置细节到常见坑位完整拆一遍。适合正在准备中高级 Java 面试的同学也适合想把 Spring Boot 缓存用好、不被缓存框架绑死的开发。所有代码和配置都是我在实际项目里跑过、踩过坑之后整理出来的可以直接参考。1. JCache 到底是什么先搞清楚这套 API 存在的意义1.1 没有标准之前缓存代码是怎么被绑死的先说一个很容易被忽略的背景。JCache 不是 Spring 的东西它是 Java 官方的缓存标准JSR-107 规范定义了javax.cache包下的一套 API。在我的早年间工作经历里项目里用缓存基本就是 Ehcache 2.x、Guava Cache、Hazelcast 各玩各的每种缓存都有自己的一套get/put/expire/listener接口。打个比方那时候的 Java 缓存世界就像每家手机厂商都有自己的充电接口手机没问题但出门得带好几根线。后来大家看不下去了JCP 组织就在 2014 年搞出了 JCache API统一了缓存的编程模型。从此以后不管底层是 Ehcache、Infinispan、Hazelcast 还是 Caffeine只要实现了 JSR-107业务代码写的是同一套接口。换缓存实现代码改动量能缩到最小这对中间件厂商和大型企业来说都是实打实的红利。所以面试时别一上来就说“JCache是一种缓存”这种回答会显得很外行。要表达出“JCache 是一套标准一套契约不是具体某一个产品更不是 Redis”。这句话是整道题的基石。1.2 JSR-107 规范定义的六大核心 APIJCache 的核心 API 都在javax.cache包下面试中能把以下几点讲到就足以证明你是真看过规范的。CachingProvider缓存的厂商入口用于创建、获取和管理 CacheManager。每个厂商都有自己的 CachingProvider 实现例如 Ehcache 的org.ehcache.jsr107.EhcacheCachingProvider。CacheManager缓存管理器可以理解为一个容器。一个 CacheManager 下面可以有多个 Cache。它负责 Cache 的生命周期管理比如创建、关闭、获取。Cache真正存数据的接口泛型为CacheK, V。它定义了一整套类似 Map 的操作但多了缓存特有的语义putIfAbsent、replace、getAndPut、invoke原子操作等。这里要注意它和 ConcurrentMap 的区别Cache 的操作有分布式的上下文并且支持通过 CacheLoader、CacheWriter 实现读写穿透。Cache.Entry缓存中的条目包含 key 和 value。ExpiryPolicy过期策略控制条目什么时候过期。CacheLoader / CacheWriter用于实现 read-through 和 write-through 模式。缓存没有数据时自动从数据源加载写缓存时同步写数据源。CacheEntryListener条目级监听器能监听创建、更新、删除、过期、淘汰等事件。那这里我补充一个看源码的小技巧Cache 接口里的invoke方法和CacheEntryProcessor是 JCache 独有的强大能力它允许在锁定的缓存条目上执行任意操作。面试如果被问“如何原子地更新缓存里的某个对象”不要说自己写 synchronized而要说用cache.invoke(key, entryProcessor)这一下就拉开了档次。1.3 Spring Cache 和 JCache 是不是同一种东西这是大多数面试者最混淆的点。Spring Cache 是 Spring 框架提供的一个缓存抽象层定义了一套注解Cacheable、CacheEvict等和统一的 CacheManager/Cache接口用来屏蔽底层不同缓存实现。它不依赖 JCache 标准也能正常工作底层完全可以是 ConcurrentMap、Redis、Caffeine 等Spring 只是做了一层封装帮你把“缓存操作”揉进了 AOP 逻辑里。JCache 则是 Java 官方的缓存编程标准它是定义在语言生态层面的一套接口。Spring Cache 抽象层完全可以适配到 JCache 上此时 Spring 的Cacheable注解底层调用的就是javax.cache接口但反过来 JCache 的注解比如CacheResult也可以在 Java EE 容器中脱离 Spring 独立使用。面试时可以给面试官画个关系业务代码 → Spring Cache 抽象注解 → JCache API标准 → 具体缓存实现Ehcache/Infinispan/Hazelcast。这里不要用“替代”来描述要说“适配与集成”。Spring Boot 中把spring.cache.type配成jcache做的事情就是用 Spring 这个壳去包住 JCache 这个标准最终让具体缓存厂商的实现落地。2. 在 Spring Boot 中集成 JCache最容易被误解的配置步骤2.1 依赖引入与版本避坑Spring Boot 集成 JCache 不需要引入一堆花里胡哨的东西基础依赖就两个dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-cache/artifactId /dependency dependency groupIdorg.ehcache/groupId artifactIdehcache/artifactId version3.10.8/version /dependency这里我特意选 Ehcache 3.x 作为示例因为它的 JSR-107 支持做得最完整文档也全。用它做学习对象能少走很多弯路。spring-boot-starter-cache会自带一个spring-context-support里面已经包含了 Spring 对 JCache 的适配器JCacheCacheManager所以不需要再加额外的适配依赖。如果你是直接用javax.cache原生 API而不是走 Spring 注解那我建议显式加上这个依赖dependency groupIdjavax.cache/groupId artifactIdcache-api/artifactId version1.1.1/version /dependency版本这块有一个特别容易被坑的点虽然 Java EE 已经改成 Jakarta EE但JCache 的包名至今依然是javax.cache没有跟随 jakarta 重命名。这意味着在 Spring Boot 3.x基于 jakarta.* 的那批里你引入 Ehcache 后照样能正常使用 javax.cache 接口不用担心包冲突。我第一次切 Spring Boot 3 时还担心这事实测下来是好的。2.2 配置文件里的三件事type、provider、cache-names在application.yml中JCache 配置主要关心的就是这么几个spring: cache: type: jcache jcache: provider: org.ehcache.jsr107.EhcacheCachingProvider cache-names: - userCache - goodsCache redis: # 如果同时配了 redis 缓存这里用于其他用途别和 jcache 混淆spring.cache.typejcache明确告诉 Spring BootCacheManager 要选用 JCache 适配器。spring.cache.jcache.provider指定 CachingProvider 的全限定类名。如果 classpath 下只放了一个 JCache 实现这一项甚至可以不配Spring Boot 会通过ServiceLoader自动发现。但如果像某些项目那样同时引入了 Ehcache 和 Hazelcast就必须显式指定否则启动时会直接报IllegalArgumentException告诉你找到了多个 CachingProvider。spring.cache.cache-names用于预创建有哪些缓存。实际项目中我建议在这里把核心缓存名列出来而不是放任业务代码随便写名字。理由很简单不列出来Spring 会在第一次访问某个缓存名时自动创建配置化管理程度低后面不好做统一的过期策略绑定。更规范的做法是放一个ehcache.xml到 classpath 下在 XML 里定义缓存模板和过期策略config xmlnshttp://www.ehcache.org/v3 xmlns:jsr107http://www.ehcache.org/v3/jsr107 service jsr107:defaults enable-managementtrue enable-statisticstrue/ /service cache aliasuserCache expiry ttl unitseconds300/ttl /expiry heap unitentries10000/heap /cache cache aliasgoodsCache expiry ttl unitseconds600/ttl /expiry heap unitentries50000/heap /cache /config只要这个文件放在 classpath 根目录Ehcache 的 JCache provider 会自动加载。配置生效后你可以在启动日志里看到类似 “Spring configured JCacheCacheManager” 的提示。2.3 用 Spring 注解还是用原生 JCache API我见过很多项目两种都在用但用混了。先说 Spring 注解方式最典型的用法Service public class UserService { Cacheable(cacheNames userCache, key #id) public User getUserById(Long id) { // 模拟数据库查询 return userMapper.selectById(id); } CacheEvict(cacheNames userCache, key #id) public void deleteUser(Long id) { userMapper.deleteById(id); } }使用前还要在配置类或启动类上加一个EnableCachingConfiguration EnableCaching public class CacheConfig { }这个注解别漏漏了你会非常困惑代码里 Cacheable 写得整整齐齐缓存就是不生效。再说原生 JCache API。如果说注解适合业务入口那原生 API 更适合做一些精细控制。比如你要在代码里显式拿到缓存对象做putIfAbsent或者是批量加载、监听事件的场景import javax.cache.Cache; import javax.cache.CacheManager; import javax.cache.Caching; CachingProvider provider Caching.getCachingProvider(); CacheManager cacheManager provider.getCacheManager(); CacheString, User userCache cacheManager.getCache(userCache, String.class, User.class); userCache.put(1001, new User(张三)); User user userCache.get(1001);要提醒一句Caching.getCachingProvider()如果没有指定参数会返回第一个被ServiceLoader加载到的 provider。如果 classpath 下存在多个实现这种裸调法不够健壮建议改成Caching.getCachingProvider(org.ehcache.jsr107.EhcacheCachingProvider)面试时可以说清楚Spring 注解适合降低使用成本、声明式拦截原生 JCache API 适合在组件底层做更细粒度的策略控制、批量预热、原子更新操作。两者不是非此即彼可以共存。2.4 JCacheConfigurer 手工装配的隐藏能力Spring Boot 自动配置对大多数项目够用但有时你有定制需求比如想动态决定 CacheManager 使用哪个 uri 路径或是在 CacheManager 创建后追加自定义的配置。这种情况下可以实现JCacheConfigurer接口Configuration EnableCaching public class CustomCacheConfig implements JCacheConfigurer { Override public CacheManager cacheManager() { CachingProvider provider Caching.getCachingProvider(org.ehcache.jsr107.EhcacheCachingProvider); return provider.getCacheManager( getClass().getResource(/ehcache-custom.xml), getClass().getClassLoader() ); } }不过这个场景平时真的用不着绝大多数项目直接靠application.yml和ehcache.xml就足够了。之所以提出来是因为面试里可能会被追问“如果自动配置不满足需求你怎么处理”你答出JCacheConfigurer重写cacheManager()面试官就知道你是真的读过 Spring Boot 源码。3. 在 Java EE 环境中集成 JCacheCDI 和标准容器的姿势3.1 Java EE 规范对 JCache 的定位Java EE现在叫 Jakarta EE规范中并没有强制要求每个应用服务器内置一个 JCache 实现但提供了非常自然的集成方式CDI 依赖注入。你可以在容器里直接注入CacheManager或Cache对象标准容器会负责它们的生命周期和配置。以 WildFly、Payara 这样的服务器为例它们内部本身就整合了 Infinispan 之类的缓存引擎并且暴露了 JCache 的 CachingProvider。你只需要在代码里Inject private javax.cache.CacheManager cacheManager; Inject private javax.cache.CacheString, User userCache;是不是比 Spring Boot 更简洁这就是 CDI 的威力但前提是你的运行时环境已经提供了 JCache 实现。如果是普通的 Tomcat 或 Jetty容器本身不提供你就得自己往 WEB-INF/lib 里塞依赖然后通过监听器启动时初始化 CacheManager。另一个 Java EE 典型的玩法是用 JCache 自带的CacheResult注解做方法级缓存。这是 JSR-107 的 CDI 扩展部分使用方式和 Spring 的 Cacheable 很像CacheResult(cacheName userCache) public User loadUser(String userId) { return dao.findById(userId); }还有配套的CacheRemove、CacheRemoveAll、CachePut注解。需要注意的是这些注解是javax.cache.annotation包下的不是 Spring 的org.springframework.cache.annotation两者千万别混着用。一个 Java EE 项目里要么走 Spring 注解、要么走 JCache 注解混用会导致拦截器不认出现“缓存看似写了但根本没触发”的诡异效果。3.2 在 Java EE 中启用 JCache 的完整链路我把常见的启用步骤整理一下场景是“使用一个标准的 Java EE 应用服务器同时使用 JCache CDI 方式”。第一步确保部署描述里声明的 bean 归档是开启的。绝大多数场景只需要在WEB-INF/beans.xml里写一个空配置beans xmlnshttp://xmlns.jcp.org/xml/ns/javaee bean-discovery-modeall /beansbean-discovery-modeall表示所有类都可以被 CDI 管理。之前踩过一个坑如果用的是默认注解模式CDI 不会扫描那些没有 bean 定义注解的类Inject CacheManager就会一直报 unsatisfied dependency。第二步确认应用服务器已经加载了 JCache provider。如果没加载需要在自己的模块里加入如 Ehcache 的依赖并且确保META-INF/services/javax.cache.spi.CachingProvider文件能被发现。第三步通过 CDI 方式注入CacheManager然后正常写业务代码。真正到生产环境动态配置缓存参数一般放在服务器的系统属性或独立配置文件里让运维能调过期时间、堆大小而不用改代码重启。Java EE 里使用 JCache 时有一个特别明显的优势它和 CDI 事件、事务机制天然集成。缓存条目监听器可以和 CDI 事件联动做数据变更通知。这一点在微服务拆分的项目里很好用某个缓存 key 更新时通过CacheEntryCreatedListener触发一个 CDI 事件让其他流程接着干活。3.3 Java EE 和 Spring Boot 的实现差异如果把两边的方案放一张表里对比下面这些差异面试时能说出来会很加分对比维度Java EE / Jakarta EESpring Boot集成方式CDI 注入声明在 beans.xmlSpring 自动配置 EnableCaching常用注解CacheResult、CacheRemoveCacheable、CacheEvict、CachePut依赖来源应用服务器内置或手动加入 libspring-boot-starter-cache 具体实现CacheManager 获取Inject 或 JNDIJCacheCacheManager 自动装配扩展入口CDI 事件、InterceptorJCacheConfigurer、CacheErrorHandler、自定义 KeyGenerator这张表并不代表谁好谁坏而是体现了两套生态的设计哲学。Java EE 偏重规范和容器托管Spring Boot 偏重自动化和约定优于配置。如果面试官问“为什么在 Spring Boot 里也能用 JCache”你只要点出“Spring Boot 实现了 JCache 的适配器本质上是把标准接口重新包装成了 Spring 管理的 Bean”就够了。4. JCache 核心机制深入几个能拉开差距的原理级细节4.1 ExpiryPolicy过期策略不是配完就完事JCache 的过期策略由ExpiryPolicy接口控制标准实现里一般有几个值ETERNAL永不过期、Modified创建或更新后固定 TTL、Accessed读取或更新后固定 TTL。配置时对Accessed别滥用因为它意味着每次 get 都会续期在高并发热点场景下会带来额外的更新时间戳开销。真正想做得精细需要自己实现接口。有一次我在一个购物车场景中需求是未登录用户加入购物车的临时数据 30 分钟过期但只要用户每次访问就自动续期登录后用户数据改成 2 小时有效期。这时标准 TTL 不够用就要写自定义 ExpiryPolicypublic class CartExpiryPolicy implements ExpiryPolicy { Override public Duration getExpiryForCreatedEntry() { return Duration.ofSeconds(1800); } Override public Duration getExpiryForUpdatedEntry() { return Duration.ofSeconds(1800); } Override public Duration getExpiryForAccessedEntry() { return Duration.ofSeconds(1800); } }然后在创建 Cache 时通过MutableConfiguration关联MutableConfigurationString, Cart config new MutableConfiguration(); config.setExpiryPolicyFactory(() - new CartExpiryPolicy());有一个容易忽略的小细节ExpiryPolicy 的工厂是通过FactoryBuilder创建的每次创建 Cache 时会生成新的策略实例。如果策略内部带有状态比如记录上一次访问时间必须注意线程安全问题。这也是为什么标准 API 里更推荐使用无状态的 Duration 配置。4.2 CacheLoader 和 CacheWriter贯穿读与写的缓存模式JCache 里最有含金量的两个接口就是CacheLoader和CacheWriter它们实现了 read-through 和 write-through 模式。CacheLoader的语义可以类比你第一次查数据时发现缓存没有主动去数据库捞。标准 JCache 把这个过程自动化了只要缓存的配置是 read-through并且 cache.get(key) 没命中JCache 实现会自动调用对应的 CacheLoader.load(key)。这个机制对防止缓存穿透很有用因为打了 read-through恶意请求打进来时最终压力会落在 CacheLoader 那一层开发者可以在这个统一入口增加并发控制和防御逻辑。CacheWriter则是写缓存的同时把数据同步到数据库等持久层适合“以缓存为主、数据库为最终存储”的场景。接口是public class UserCacheWriter implements CacheWriterLong, User { Override public void write(Cache.Entry? extends Long, ? extends User entry) { userMapper.save(entry.getKey(), entry.getValue()); } Override public void delete(Object key) { userMapper.deleteById((Long) key); } }实际用的时候配置中要把isReadThrough、isWriteThrough开启MutableConfigurationLong, User config new MutableConfiguration(); config.setTypes(Long.class, User.class) .setCacheLoaderFactory(() - new UserCacheLoader()) .setCacheWriterFactory(() - new UserCacheWriter()) .setReadThrough(true) .setWriteThrough(true);我个人的建议是业务单纯的读多写少场景不要乱开 write-through它会把每一次缓存写操作都变成同步的存储层操作写放大很严重。更常见的设计是 read-through 配合 CacheLoader写的时候用 CacheWriter 做异步补偿或者干脆只更新缓存、由业务层保证最终一致性。4.3 监听器与 CacheStatistics别忽视这两个辅助能力CacheEntryListener一般被归类为功能增强但因为工作中排查问题经常需要它我单独说几点。监听器支持的事件类型包括 CREATED、UPDATED、REMOVED、EXPIRED、EVICTED。注册方式CacheEntryCreatedListenerLong, User createdListener (events) - { for (CacheEntryEvent? extends Long, ? extends User event : events) { // 处理批量事件 } }; cache.registerCacheEntryListener( new MutableCacheEntryListenerConfiguration( () - createdListener, null, // filter true, // old value required false // synchronous ) );注意这里synchronousfalse表示异步监听生产环境除非有强一致的需求否则我建议使用异步。监听器在大型缓存库里处理的可能是成百上千个事件同步执行会拖慢缓存主流程。CacheStatistics则是做性能判断的关键依赖。JCache 的统计走 JMX 方向CacheManagerMXBean和CacheStatisticsMXBean可以暴露缓存命中率、平均 get 时间、淘汰数量等数据。配置时只需要在 ehcache.xml 的 jsr107 节点打开 enable-statistics然后通过 JConsole 或 Spring Boot Admin 的 JMX 面板查看。在系统性能调优或者面试聊“如何判断缓存是否需要扩容”时这些数据说话比猜靠谱得多。5. 实战里那些容易踩的坑这些经验比配置本身更值钱5.1 缓存穿透、击穿、雪崩在 JCache 下的应对我在刚开始部署 JCache 时天真地以为加入缓存就万事大吉结果线上真实流量一冲次要问题全暴露了。三个经典场景在 JCache 里怎么处理我给出个人实操结论缓存穿透查一个根本不存在的数据每次请求都穿过缓存到达数据库。JCache 的Cache.get对于不存在的数据返回 null此时会触发 CacheLoader 加载但加载结果也是 null 的话JCache 并不会缓存 null。最快的处理方式是在 CacheLoader 中做空值标记或者用 Optional 包一层配合Cacheable(unless #result null)让空值不进缓存。缓存击穿热点 key 失效的瞬间大量请求进来。可以用 JCache 的putIfAbsent配合自定义加载锁只让一个线程去加载数据。其实 Ehcache 等实现内部对单 key 的读加载做了加锁所以不需要自己再写一套 distributed lock。缓存雪崩大量 key 在同一个时间段过期。解决思路是配置过期时间时加一个随机量错开过期时间。但 JCache 的 ttl 配置是固定值如果平台没有随机 TTL 扩展你只能在创建 Cache 后对不同的 key 显式设置不同的 ExpiryPolicy或者结合业务对 key 做分组到不同 Cache。5.2 本地缓存与分布式缓存的边界问题JCache 本身只是一套 API它不规定底层是本地内存还是分布式集群。Ehcache 3.x 默认是本地堆缓存Hazelcast、Infinispan 则有分布式实现同样符合 JSR-107。这就引出一个重要问题你在 Spring Boot 里同时依赖了 spring-boot-starter-cache 和 ehcache默认得到的 JCacheCacheManager 只是节点本地缓存。当你的应用部署了多个实例用户请求被负载均衡打到不同节点时每个节点看到的缓存不共享。我之前有个血泪教训用户登录 token 缓存在本地 JCache 里Redis 负责其他数据结果用户登录 session 在节点 A 保存后下一次请求被负载均衡转发到节点 B直接从缓存中查不到登录态丢失。后来我采用的方案是本地 JCache 只保存“不要求全局一致”的热点数据比如字典表、商品详情全局共享、强一致要求高的数据放到分布式缓存组件。JCache 里的分布式实现也可以解决这个问题但需要你认真评估网络序列化、数据一致性带来的复杂度。5.3 注解失效、自调用、序列化这些老坑一个都别踩先说注解失效问题。Spring 的Cacheable基于 AOP 代理实现如果在一个类内部调用带缓存注解的方法例如public void processUser(Long id) { this.getUserById(id); // 缓存不生效 }因为调用发生在同一个类内部对象引用是原始对象而非代理对象拦截器根本不会执行。解决方式很简单把方法拆到另一个 Bean或者在配置里开启 exposeProxy 后使用AopContext.currentProxy()。这个坑我面试时问了很多人十个有四个没意识到。然后是序列化问题。Ehcache 等 JCache 实现默认会把 value 变成字节流存储。如果你的 value 对象没有实现Serializable运行起来会抛NonSerializableException这类错误。这个坑在本地堆缓存时并不明显因为某些实现拿到的是对象引用一旦开启磁盘持久化或者切换到分布式的 JCache 实现就会立刻暴露。所以写缓存的实体建议都老老实实实现 Serializable并且定义好 serialVersionUID。最后是事务一致性问题。Spring 的事务提交通常发生在缓存注解逻辑之后如果你的业务是先查数据库更新数据库再更新缓存但你用的是默认事务边界缓存更新可能发生在数据库事务提交之前。如果数据库事务后来回滚了缓存里的数据就是脏的。我个人习惯是把缓存更新动作放在事务提交后的TransactionalEventListener(phase AFTER_COMMIT)里执行保证最终一致性。5.4 缓存预热与冷启动问题JCache 提供了loadAll方法可以批量加载但它只负责把数据从 CacheLoader 拉到缓存里不负责决定哪些是要加载的热门 key。很多人一上来就把全表 loadAll结果缓存膨胀、内存被打满。合理的做法是结合业务数据统计确定热点 key 集合在应用启动完成之后用一个后台线程慢慢加载最好还要控制并发加载速度。比如这样ExecutorService executor Executors.newFixedThreadPool(4); ArrayListLong hotKeys analyzeHotKeys(); for (ListLong batch : Lists.partition(hotKeys, 100)) { executor.submit(() - cache.loadAll(batch, true, null)); }6. 面试回答框架30 秒讲清楚、3 分钟讲深入回到那道每日一题。面试官问“在 Java EE 或 Spring Boot 环境中如何集成和启用 JCache”答题时要有主次感第一步先一句话定义 JCache。它是 JSR-107 标准的 Java 缓存 API不是具体缓存产品而是一套统一的缓存编程接口核心元素包括 CachingProvider、CacheManager、Cache、ExpiryPolicy、CacheLoader/CacheWriter 等。第二步分环境说明集成方式。Java EE 环境中可以通过 CDI 直接注入 CacheManager 或 Cache配合CacheResult、CacheRemove注解使用Spring Boot 环境中引入spring-boot-starter-cache和 JCache 实现如 Ehcache 3.x配置spring.cache.typejcache指定spring.cache.jcache.provider加入EnableCaching后即可使用Cacheable。第三步补充自动发现多 provider 的处理细节。如果 classpath 存在多个 CachingProvider必须在配置里指定spring.cache.jcache.provider否则启动报错。第四步进入加分项。主动说说CacheLoader与CacheWriter对 read-through / write-through 的支持CacheEntryListener和 CacheStatistics 在运行时监控中的作用。再提到 Spring Cache 是抽象层、JCache 是标准 API二者是适配关系这番话足以让面试官感觉到你不是背题。最后如果面试官追问“JCache 和 Spring Cache 怎么选”我的个人倾向是普通业务接口用 Spring Cache 注解足够它和事务、SpEL、错误处理结合得更好如果你在写公共缓存组件、缓存中间件需要标准 API 层面的可移植性时直接基于 javax.cache 编程才是正道。这也是 JCache 存在的最大价值。我在经历了本地缓存到多级缓存、从 Ehcache 2.x 迁到 Ehcache 3.x 的折腾后最大的体会就是缓存框架本身从来不是核心难点难点在于你对数据一致性、过期时间、并发模型的理解。JCache 作为一套标准最有意义的不是省了几行代码而是逼着你把上面这些问题按统一的方式思考一遍。面试题刷到这块时别只背配置把规范读一遍、把 CacheLoader 玩熟收获会大得多。
返回列表