@Cache注释

CacheConcurrencyStrategy.NONE,不适用,默认
CacheConcurrencyStrategy.READ_ONLY,只读模式,在此模式下,如果对数据进行更新操作,会有异常(对于不发生改变的数据使用);
CacheConcurrencyStrategy.READ_WRITE,读写模式在更新缓存的时候会把缓存里面的数据换成一个锁,其它事务如果去取相应的缓存数据,发现被锁了,直接就去数据库查询(基于时间戳判定机制,,对于数据同步要求严格的情况,使用频繁);
CacheConcurrencyStrategy.NONSTRICT_READ_WRITE,不严格的读写模式则不会的缓存数据加锁(更新不频繁几个小时或更长);
CacheConcurrencyStrategy.TRANSACTIONAL,事务模式指缓存支持事务,当事务回滚时,缓存也能回滚,只支持JTA环境。

<think> 我们正在讨论的问题是:在同一个类中,方法a调用方法b,方法b上使用了@Cacheable注解,但缓存未生效。用户询问通过注入该类的接口并添加@Lazy注解是否能够解决缓存失效问题并避免循环依赖。 核心问题分析: 1. 缓存失效原因:在同一个类中,方法a直接调用方法b(即this.b())时,是目标对象内部的调用,不会经过代理对象,因此无法触发AOP增强(包括缓存、事务等)。这是Spring AOP的机制决定的,因为AOP代理是通过拦截器在代理对象上执行的,而内部调用不会走代理。 2. 解决方案思路: - 通常的解决方案是:让方法a调用代理对象的方法b,而不是直接调用。 - 常用方法有两种: a. 通过AopContext获取当前代理对象,然后调用其方法b(需要开启exposeProxy)。 b. 通过依赖注入自身(或自身接口)的代理对象,然后通过这个代理对象调用方法b。 3. 用户提到的方案:注入该类的接口并添加@Lazy注解。 - 解释:注入自身的代理对象(通过接口注入),并使用@Lazy避免循环依赖问题。 具体分析: - 注入自身接口(假设为MyService)并添加@Lazy,例如: @Service public class MyServiceImpl implements MyService { @Autowired @Lazy private MyService self; // 注入自身的代理对象 public void a() { // 使用注入的代理对象调用b方法 self.b(); // 这样会走代理,从而缓存生效 } @Cacheable("myCache") public void b() { // ... } } - 为什么能解决缓存失效? 因为通过注入的代理对象(self)调用b方法,此时调用的是代理对象的方法,所以会被AOP拦截,从而触发缓存逻辑。 - 为什么加@Lazy? 避免循环依赖:因为MyServiceImpl在创建时需要注入自身,如果不加@Lazy,则会出现循环依赖(自己依赖自己)。Spring默认支持通过setter注入解决循环依赖(使用三级缓存),但构造函数注入的循环依赖无法解决。而使用@Lazy可以延迟注入,即先创建一个代理对象注入,等到真正使用时再初始化,从而避免循环依赖。 4. 注意事项: - 该方法适用于代理模式为JDK动态代理(基于接口)的情况。如果使用CGLIB(代理类),则注入自身类型(而不是接口)也可以,但同样需要@Lazy。 - 但注意:如果使用CGLIB,且注入的是具体类,则要求该类没有被final修饰(因为CGLIB通过生成子类代理)。 5. 替代方案: - 方案1:使用AopContext.currentProxy()(需要开启exposeProxy) 在配置类上添加:@EnableAspectJAutoProxy(exposeProxy = true) 然后调用: ((MyService) AopContext.currentProxy()).b(); - 方案2:将方法b拆分到另一个Bean中,然后通过注入那个Bean来调用(推荐,符合单一职责)。 6. 关于循环依赖: - 使用@Lazy注解注入自身代理是解决此类循环依赖的常用手段。 - Spring默认支持setter注入的循环依赖(通过三级缓存),但构造函数注入的循环依赖无法解决。而使用@Lazy可以打破循环,因为它不会立即初始化注入的Bean,而是在第一次使用时初始化。 结论: 使用注入接口(或自身)并添加@Lazy注解的方式,可以使得在同一个类内通过代理对象调用b方法,从而让@Cacheable生效,并且能够避免循环依赖问题。 但是,请注意: 这种方式虽然可行,但会使代码与Spring框架产生一定耦合(需要注入自身),并且逻辑上可能不够清晰。因此,如果可能,更推荐将方法b放到另一个Bean中。 示例代码: ```java public interface MyService { void a(); void b(); } @Service public class MyServiceImpl implements MyService { // 注意:这里注入的是自身的代理(通过接口) @Autowired @Lazy private MyService self; @Override public void a() { // 通过代理调用b self.b(); // 缓存生效 } @Cacheable("myCache") @Override public void b() { // 业务逻辑 } } ``` 相关问题: 1. 除了使用@Lazy注入自身代理,还有哪些方法可以解决Spring AOP的“自调用”问题? 2. Spring如何处理循环依赖?三级缓存的具体机制是什么? 3. 在Spring中,如何选择JDK动态代理和CGLIB代理?它们对自调用问题的影响有何不同? 4. 为什么Spring的缓存、事务等注解在同一个类内部调用时不生效? 5. 使用AopContext.currentProxy()方法需要注意哪些问题? </think> 针对同一个类内方法调用导致`@Cacheable`失效的问题,结合注入接口添加`@Lazy`的方案分析如下: ### 问题本质:自调用导致代理失效 在Spring AOP中,当类内方法`a()`直接调用`b()`时: ```java class Service { public void a() { b(); // ⚠️ 直接调用相当于 this.b(),绕过代理 } @Cacheable("cache") public Object b() {...} } ``` $$ \text{调用路径:} a() \xrightarrow{\text{this}} b() \quad (\text{未经过代理}) $$ 导致缓存拦截器无法触发[^1]。 --- ### 解决方案:注入自身代理 + `@Lazy` #### 1. **核心实现代码** ```java @Service public class MyService { @Autowired @Lazy // 关键注解 private MyService selfProxy; // 注入自身代理对象 public void a() { selfProxy.b(); // 通过代理对象调用 } @Cacheable("myCache") public Object b() {...} } ``` #### 2. **生效原理** | 组件 | 作用 | |-------------------|----------------------------------------------------------------------| | `selfProxy` | Spring注入的**代理对象**(非原始Bean) | | `@Lazy` | 延迟初始化,打破 `MyService` ↔ `MyService` 的循环依赖链 | | `selfProxy.b()` | 调用路径:`a()` → **代理对象** → 缓存拦截器 → 实际`b()`方法 | #### 3. **避免循环依赖机制** ```mermaid graph LR A[创建MyService原始对象] --> B[注入selfProxy代理] B --> C[初始化完成] C --> D[调用a方法时才触发selfProxy初始化] ``` - **循环依赖化解**:`@Lazy`将依赖推迟到运行时,避开启动时的死锁检测 - **代理有效性**:注入的`selfProxy`是CGLIB/JDK动态代理对象,携带缓存拦截器 --- ### 注意事项 1. **接口必要性** - 若使用**JDK动态代理**:必须通过接口注入(`private MyServiceInterface selfProxy`) - 若使用**CGLIB代理**(`proxyTargetClass=true`):可直接注入实现类 ```java @EnableAspectJAutoProxy(proxyTargetClass = true) // CGLIB模式 ``` 2. **常见陷阱** - **`final`方法失效**:若`b()`是`final`方法,CGLIB代理无法增强 - **序列化异常**:代理对象包含Spring特定字段,网络传输需`Serializable`适配 3. **替代方案对比** | 方案 | 缓存生效 | 避免循环依赖 | 代码侵入性 | |--------------------------|----------|--------------|------------| | 注入接口+`@Lazy` | ✅ | ✅ | 中 | | `AopContext.currentProxy()` | ✅ | ❌ | 高 | | 拆分类职责 | ✅ | ✅ | 低(推荐) | --- ### 结论 ✅ **可行方案**:注入接口(或实现类)并添加`@Lazy`注解,**能同时解决**: 1. 自调用导致的`@Cacheable`失效问题(通过代理对象触发拦截) 2. Bean循环依赖问题(延迟初始化打破依赖环) > 最佳实践优先考虑重构代码(如将`b()`抽离到独立Bean),若受限于架构再采用此方案[^2]。 --- ### 相关问题 1. 使用`@Lazy`注入自身代理时,Spring的Bean生命周期会发生什么变化? 2. 如何验证一个方法调用是否真正通过了Spring AOP代理? 3. 在分布式场景下,CGLIB代理对象通过RPC传输会引发什么问题? 4. 为什么Spring默认不直接支持自调用场景的AOP增强? 5. 除了缓存和事务,还有哪些Spring AOP功能在自调用时会失效? [^1]: Spring AOP代理机制限制 [^2]: 延迟注入与代理设计的最佳实践
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值