Spring Cache 工作原理:代理对象机制详解

Spring Cache 的核心原理是基于 Spring AOP(面向切面编程)实现的,主要通过动态代理机制来实现缓存功能。让我详细解释一下这个过程:

1. 代理模式基础

Spring Cache 使用两种代理机制:

  • JDK 动态代理:适用于实现了接口的类
  • CGLIB 代理:适用于没有实现接口的类

2. 代理对象创建过程

当 Spring 容器启动时,会扫描所有带有缓存注解(如 @Cacheable@CacheEvict 等)的 Bean,并为它们创建代理对象:

// 原始的Service类
@Service
public class DishService {
    
    @Cacheable("dishCache")
    public List<Dish> getDishesByCategory(Long categoryId) {
        // 实际的数据库查询逻辑
        return dishMapper.findByCategory(categoryId);
    }
}

Spring 会为这个 DishService 创建一个代理对象,而不是直接使用原始对象。

3. 代理对象工作流程

当调用带有 @Cacheable 注解的方法时,执行流程如下:

  1. 调用方调用代理对象的方法
  2. 代理对象拦截方法调用
  3. 检查缓存中是否存在对应的数据
  4. 如果存在,直接返回缓存数据(不执行原方法)
  5. 如果不存在,执行原方法,获取结果后存入缓存并返回
// 代理对象的简化实现逻辑
public class CacheProxy {
    
    private DishService target; // 原始对象
    private Cache cache; // 缓存
    
    public List<Dish> getDishesByCategory(Long categoryId) {
        // 1. 生成缓存key
        String key = generateKey("dishCache", categoryId);
        
        // 2. 检查缓存
        List<Dish> cachedResult = cache.get(key);
        if (cachedResult != null) {
            // 3. 缓存命中,直接返回
            return cachedResult;
        }
        
        // 4. 缓存未命中,调用原始方法
        List<Dish> result = target.getDishesByCategory(categoryId);
        
        // 5. 将结果存入缓存
        cache.put(key, result);
        
        // 6. 返回结果
        return result;
    }
}

4. Spring Cache 核心组件

Spring Cache 的实现主要依赖以下几个核心组件:

CacheManager

管理多个 Cache 实例,是缓存的统一入口:

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        RedisCacheManager cacheManager = RedisCacheManager.builder()
            .cacheDefaults(cacheConfiguration())
            .build();
        return cacheManager;
    }
}

CacheInterceptor

缓存拦截器,是实现缓存逻辑的核心:

// 简化的缓存拦截器逻辑
public class CacheInterceptor implements MethodInterceptor {
    
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 1. 解析缓存注解
        CacheOperation cacheOperation = parseCacheAnnotation(invocation);
        
        // 2. 生成缓存key
        Object key = generateKey(cacheOperation, invocation);
        
        // 3. 根据不同注解类型执行不同逻辑
        if (cacheOperation instanceof CacheableOperation) {
            // @Cacheable逻辑
            return handleCacheable(invocation, cacheOperation, key);
        } else if (cacheOperation instanceof CacheEvictOperation) {
            // @CacheEvict逻辑
            return handleCacheEvict(invocation, cacheOperation, key);
        }
        
        // 4. 执行原方法
        return invocation.proceed();
    }
}

5. 实际应用示例

让我们看一个更完整的示例,展示代理对象如何工作:

@Service
public class DishServiceImpl implements DishService {
    
    @Autowired
    private DishMapper dishMapper;
    
    // 这个方法会被代理
    @Cacheable(value = "dishes", key = "#categoryId")
    public List<Dish> getDishesByCategory(Long categoryId) {
        System.out.println("执行数据库查询..."); // 这行可以帮助我们观察是否真正执行了方法
        return dishMapper.findByCategory(categoryId);
    }
    
    // 这个方法也会被代理
    @CachePut(value = "dishes", key = "#dish.categoryId")
    public Dish updateDish(Dish dish) {
        dishMapper.update(dish);
        return dish;
    }
    
    // 这个方法同样会被代理
    @CacheEvict(value = "dishes", key = "#categoryId")
    public void removeDishCache(Long categoryId) {
        // 只是清除缓存,不执行其他逻辑
    }
}

调用过程:

@RestController
public class DishController {
    
    @Autowired
    private DishService dishService; // 实际注入的是代理对象
    
    @GetMapping("/dishes/{categoryId}")
    public List<Dish> getDishes(@PathVariable Long categoryId) {
        // 第一次调用:执行方法,打印"执行数据库查询...",结果被缓存
        List<Dish> dishes1 = dishService.getDishesByCategory(categoryId);
        
        // 第二次调用:直接从缓存返回,不会打印"执行数据库查询..."
        List<Dish> dishes2 = dishService.getDishesByCategory(categoryId);
        
        return dishes2;
    }
}

6. 代理模式的限制

需要注意的是,Spring Cache 的代理机制有一些限制:

  1. 内部调用问题
@Service
public class DishService {
    
    // 这个方法上的@Cacheable不会生效,因为是内部调用,不经过代理
    public List<Dish> getAllDishes() {
        return getDishesByCategory(1L); // 内部调用,不经过代理
    }
    
    @Cacheable("dishes")
    public List<Dish> getDishesByCategory(Long categoryId) {
        return dishMapper.findByCategory(categoryId);
    }
}
  1. 只能代理 public 方法
@Service
public class DishService {
    
    @Cacheable("dishes") 
    private List<Dish> getPrivateDishes(Long categoryId) { // 不会生效
        return dishMapper.findByCategory(categoryId);
    }
    
    @Cacheable("dishes")
    public List<Dish> getPublicDishes(Long categoryId) { // 会生效
        return dishMapper.findByCategory(categoryId);
    }
}

总结

Spring Cache 通过动态代理机制实现缓存功能:

  1. 在应用启动时,Spring 为带有缓存注解的 Bean 创建代理对象
  2. 当调用这些方法时,代理对象会拦截调用并执行缓存逻辑
  3. 根据注解类型(@Cacheable、@CacheEvict 等)执行相应的缓存操作
  4. 通过 CacheManager 和具体的 Cache 实现管理缓存数据

这种设计使得开发者可以非常简单地通过添加注解的方式实现缓存功能,而不需要修改业务逻辑代码,实现了缓存逻辑与业务逻辑的解耦。