Spring的Bean内部方法调用无法使用AOP切面(CacheAble注解失效)

Spring的Bean内部方法调用无法使用AOP切面(CacheAble注解失效)

前言

今天在使用Spring cache的Cacheable注解的过程中遇见了一个Cacheable注解失效的问题,检查问题发生的原因是因为Spring的Cacheable注解是基于Spring AOP实现的,但是类内部方法互相调用时不会被Spring AOP拦截的,所以导致被调用方法的Cacheable注解失效,特此记录。

问题复现
@Service
public class UserServiceImpl{
    @Override
    public User detail(Long id) {

        // 校验信息
        if (id == null || id == 0) {
            return ApiResult.instance().fail(UserResultEnum.USER_ID_NULL);
        }
        User user = this.selectById(id);
        if (user == null) {
            return ApiResult.instance().fail(UserResultEnum.USER_NULL);
        }
        return user;
    }

    @Override
    @Cacheable(value = "user",condition = "#id != null", key = "'user'.concat(#id.toString())")
    public User selectById(Serializable id){
        return super.selectById(id);
    }
}

上述代码在使用this.selectById的时候Cacheable注解是无效的,解决办法如下:

  • 写一个工具类SpringContextUtil实现ApplicationContextAware接口
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext){
        SpringContextUtil.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext(){
        return applicationContext;
    }

    public static Object getBean(Class var1) throws BeansException {
        return applicationContext.getBean(var1);
    }
}
  • 在Spring Boot启动的时候将ApplicationContext注入SpringContextUtil
public class AuthServiceApplication {

    public static void main(String[] args) {
        SpringContextUtil springContextUtil = new SpringContextUtil();
        ApplicationContext applicationContext = SpringApplication.run(AuthServiceApplication.class, args);
        springContextUtil.setApplicationContext(applicationContext);
    }
}
  • 在UserServiceImpl方法中使用
@Service
public class UserServiceImpl{
    @Override
    public User detail(Long id) {

        // 校验信息
        if (id == null || id == 0) {
            return ApiResult.instance().fail(UserResultEnum.USER_ID_NULL);
        }
        //注入当前Bean使得调用内部方法也被SpringAOP拦截
        IUserService userService = (IUserService) SpringContextUtil.getBean(this.getClass());
        User user = userService.selectById(id);
        if (user == null) {
            return ApiResult.instance().fail(UserResultEnum.USER_NULL);
        }
        return user;
    }

    @Override
    @Cacheable(value = "user",condition = "#id != null", key = "'user'.concat(#id.toString())")
    public User selectById(Serializable id){
        return super.selectById(id);
    }
}

这样就可以解决Bean内部方法调用不被Spring AOP拦截的问题

### @Cacheable 缓存注解失效的原因分析 #### 失效原因 @Cacheable 注解失效的主要原因是由于 SpringAOP 切面机制依赖于代理对象。当方法被同一类内的其他方法调用时,实际上并未经过代理层,因此无法触发缓存逻辑[^1]。 此外,如果项目中未正确配置代理模式(如 JDK 动态代理或 CGLIB),也可能导致 @Cacheable 注解的功能未能正常工作[^4]。 --- ### 解决方案 #### 方法一:调整调用方式 确保目标方法通过代理对象调用,而是直接在同一类内调用。例如,在服务间交互时,应通过接口注入的方式实现跨 Bean 调用,而非直接调用本类中的方法。 ```java @Service public class MyService { @Autowired private MyService self; // 自身注入 public void callCachedMethod() { self.cachedMethod(); // 通过代理对象调用 } @Cacheable("myCache") public String cachedMethod() { return "Result"; } } ``` --- #### 方法二:使用 `AopContext.currentProxy()` 获取当前代理实例 在需要的地方显式地获取当前代理对象并调用目标方法。这种方式适用于必须在同一个类中调用的情况[^3]。 ```java import org.springframework.aop.framework.AopContext; @Service public class MyService { @Cacheable("myCache") public String cachedMethod() { return "Result"; } public void internalCall() { ((MyService) AopContext.currentProxy()).cachedMethod(); } } ``` > **注意**:此方法需启用 `expose-proxy` 属性为 true 才能正常使用。 --- #### 方法三:修改代理策略 Spring 默认会根据是否有接口决定采用哪种代理方式(JDK 动态代理或 CGLIB)。为了兼容更多场景,可以强制设置为 CGLIB 模式: ```properties spring.aop.proxy-target-class=true ``` CGLIB 方式的优点在于它可以直接代理具体类而局限于接口,从而减少因代理方式同而导致的潜在问题。 --- ### 配置 Ehcache 并验证缓存管理器 除了上述代理相关问题外,还需确认缓存管理器是否已正确定义。通常情况下,Spring 使用 `EhCacheCacheManager` 来集成第三方缓存工具。以下是典型的配置示例[^5]: ```xml <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache.xml"/> </bean> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <constructor-arg ref="ehCacheManager"/> </bean> ``` 如果没有提供自定义配置文件,默认会加载 `ehcache-failsafe.xml` 或者抛出异常提示找到配置文件。 --- ### 总结 @Cacheable 注解失效的核心问题是调用链路未经过代理对象。解决办法包括调整调用关系、利用 `AopContext.currentProxy()` 显式获取代理实例以及合理配置代理策略。同时,还需要检查缓存管理器及其关联组件是否按预期初始化完成。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值