Spring缓存POJO Getter失效:拍卖系统中@Cacheable无法缓存getCurrentPrice
嘿,我来帮你解决这个头疼的问题~你遇到的@Cacheable在抽象父类getCurrentPrice()上不生效的情况,其实不是Spring Cache不支持这个场景,大概率是配置或者代理机制的细节没处理到位,咱们一步步来捋:
先搞清楚Spring Cache的核心限制
Spring Cache是基于AOP代理实现的,所以注解生效有几个前提,尤其是在父类方法上使用时:
- 被注解的方法必须是
public的(你的代码满足这点) - 调用方法的对象必须是Spring容器管理的代理对象,不能是直接new出来的,也不能是内部方法调用(同一个类里的方法直接调用会绕过代理)
- 继承父类的子类必须被Spring容器接管(也就是加了
@Component/@Service这类注解)
大概率踩中的几个坑
1. 子类没被Spring管理
如果你的PricedEntity子类(比如具体的拍卖商品类)没有加上@Component或者对应的Spring stereotype注解,Spring根本没法为它创建代理对象,@Cacheable自然不会触发。
2. 内部方法调用绕过了代理
比如在子类的某个方法里直接调用getCurrentPrice(),这种内部调用是不会经过Spring代理的,注解逻辑不会执行。举个反例:
@Component public class AuctionProduct extends PricedEntity { public void someMethod() { // 这里直接调用,不会触发缓存! Price price = getCurrentPrice(); } }
3. 缓存配置没到位
- 你有没有在配置类或者启动类上加
@EnableCaching?这个注解是开启Spring Cache功能的开关,没加的话所有缓存注解都不会生效。 - 检查Ehcache的配置文件(比如
ehcache.xml),有没有定义你在@Cacheable里指定的缓存名称?比如如果用了@Cacheable(cacheNames = "priceCache"),配置里必须有对应的<cache name="priceCache"...>节点,不然缓存找不到容器,注解也白搭。 key的配置是否合理?如果每个商品的价格是独立的,key必须包含商品的唯一标识(比如id),不然所有商品的价格都会存在同一个缓存key里,导致数据混乱。比如:@Cacheable(cacheNames = "priceCache", key = "#root.target.id")
4. 代理方式的问题
Spring默认用JDK动态代理(基于接口),如果你的子类没有实现接口,需要确保Spring Boot启用了CGLIB代理(现在Spring Boot默认已经开启,但可以确认下spring.aop.proxy-target-class=true是否在配置里)。
针对性的解决方案
方案1:确保Spring代理生效
- 给所有继承
PricedEntity的子类加上@Component注解,让Spring创建代理对象。 - 如果是内部调用问题,改成通过Spring上下文获取代理对象来调用:
@Autowired private ApplicationContext context; public void someMethod() { // 获取自身的代理对象 AuctionProduct proxy = context.getBean(AuctionProduct.class); Price price = proxy.getCurrentPrice(); }
方案2:完善缓存配置
- 在启动类或配置类上加
@EnableCaching:@SpringBootApplication @EnableCaching public class AuctionApplication { public static void main(String[] args) { SpringApplication.run(AuctionApplication.class, args); } } - 配置Ehcache的过期时间(刚好匹配你“每隔n分钟重新计算价格”的需求),比如
ehcache.xml:<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <cache name="priceCache" maxEntriesLocalHeap="1000" timeToLiveSeconds="300"/> <!-- 5分钟自动过期,下次调用重新计算 --> </config>
方案3:手动管理缓存(更灵活的场景)
如果需要更精细的控制(比如根据业务逻辑主动刷新缓存),可以直接使用Ehcache的API:
@Autowired private CacheManager cacheManager; public Price getCurrentPrice(Long productId) { Cache cache = cacheManager.getCache("priceCache"); Cache.ValueWrapper wrapper = cache.get(productId); if (wrapper != null) { return (Price) wrapper.get(); } // 计算价格 Price computedPrice = computePrice(productId); cache.put(productId, computedPrice); return computedPrice; }
这种方式完全绕开Spring Cache的代理限制,适合复杂的缓存逻辑。
总结
Spring Cache完全支持在抽象父类方法上做缓存,只是要注意代理的规则和配置细节。结合你的业务场景(价格每隔n分钟变化),最省心的方式是通过Ehcache的timeToLiveSeconds配置自动过期,配合正确的Spring代理配置,就能实现自动刷新价格的需求啦。
内容的提问来源于stack exchange,提问作者cirkopel

