spring cache 的常规使用

Spring Cache 实现与优化

spring cache

spring 提供了cache的封装

需要引入:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.3.9.RELEASE</version>
        </dependency>

根据项目需要 可以设置cache 的具体实现,有如下类型

public enum CacheType {

   /**
    * Generic caching using 'Cache' beans from the context.
    */
   GENERIC,

   /**
    * JCache (JSR-107) backed caching.
    */
   JCACHE,

   /**
    * EhCache backed caching.
    */
   EHCACHE,

   /**
    * Hazelcast backed caching.
    */
   HAZELCAST,

   /**
    * Infinispan backed caching.
    */
   INFINISPAN,

   /**
    * Couchbase backed caching.
    */
   COUCHBASE,

   /**
    * Redis backed caching.
    */
   REDIS,

   /**
    * Caffeine backed caching.
    */
   CAFFEINE,

   /**
    * Simple in-memory caching.
    */
   SIMPLE,

   /**
    * No caching.
    */
   NONE

}

学习掌握的重点集中在redis 和CAFFEINE

redis 是分布式系统必不可少的缓存上实现方式,spring cache 的封装逻辑可以满足大部分如

配置文件、数据字典的缓存逻辑, 避免 数据库访问,或者程序直接的调用

redis的重点是提供分布式的缓存 机制,缓存超时,但是内存回收则是redis 服务自己控制
caffeine 组件则是提供了更加丰富的内存回收控制机制。

spring cache redis 实现

网上大部分资料都需要自己实现

CacheManager

@Bean
public CacheManager cacheManager(RedisConnectionFactory lettuceConnectionFactory) {
    RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
    // 设置缓存管理器管理的缓存的默认过期时间
    defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofSeconds(defaultExpireTime))
            // 设置 key为string序列化
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            // 设置value为json序列化
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
            // 不缓存空值
            .disableCachingNullValues();

    Set<String> cacheNames = new HashSet<>();
    cacheNames.add(userCacheName);

    // 对每个缓存空间应用不同的配置
    Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
    configMap.put(userCacheName, defaultCacheConfig.entryTtl(Duration.ofSeconds(userCacheExpireTime)));
    configMap.put("users", defaultCacheConfig.entryTtl(Duration.ofSeconds(400)));

    RedisCacheManager cacheManager = RedisCacheManager.builder(lettuceConnectionFactory)
            .cacheDefaults(defaultCacheConfig)
            .initialCacheNames(cacheNames)
            .withInitialCacheConfigurations(configMap)
            .build();
    return cacheManager;
}

KeyGenerator

@Bean
public KeyGenerator keyGenerator() {
    /**
     * target: 类
     * method: 方法
     * params: 方法参数
     */
    return (target, method, params) -> {
        //获取代理对象的最终目标对象
        StringBuilder sb = new StringBuilder();
        sb.append(target.getClass().getSimpleName()).append(":");
        sb.append(method.getName()).append(":");
        //调用SimpleKey的key生成器
        Object key = SimpleKeyGenerator.generateKey(params);
        return sb.append(key);
    };

另外有人开源了自定义的

public interface Cache

逻辑,实现了一级缓存使用 caffeine 在服务的内存中,二级缓存在redis 中共享,平衡了内存资源的使用

参考源码:https://github.com/xiaolyuh/layering-cache

反例是一种在service 中实现的缓存逻辑

反例可以实现功能但是 封装的不够优雅,代码复用性不佳

https://blog.youkuaiyun.com/Weixiaohuai/article/details/121314530

配置文件

配置文件:

spring:
  cache:
    redis:
      key-prefix: "sp:"
      time-to-live: 800
    type: redis

注意需要增加一个注解 才能使配置文件生效

@CacheConfig(cacheNames = "users")
public class UserService {....}

参考文章:https://www.fancv.com/article/1663491724

您的理解非常准确——**Spring Cache 确实通过 AOP 代理实现**,但关键在于它 **并不需要探知方法内部逻辑**,而是通过 **方法调用链的拦截** 和 **缓存键的匹配** 实现复用。以下是具体原理的分步拆解: --- ### 1. **Spring Cache 的 AOP 拦截机制** #### (1)代理层的工作流程 ```java // 伪代码:简化版的 AOP 拦截逻辑 public class CacheInterceptor extends MethodInterceptor { public Object invoke(MethodInvocation invocation) { // 1. 根据注解生成缓存键(如 "userByEmail::a@test.com") CacheKey cacheKey = generateKey(invocation.getArguments()); // 2. 检查缓存是否存在该键 if (cache.get(cacheKey) != null) { return cache.get(cacheKey); // 直接返回缓存值 } // 3. 缓存未命中 → 执行原始方法 Object result = invocation.proceed(); // 4. 将返回值存入缓存 cache.put(cacheKey, result); return result; } } ``` #### (2)关键结论: - **AOP 只关心方法输入(参数)和输出(返回值)**,不解析方法内部代码。 - `findOneByEmail` 的缓存复用完全依赖于 **相同的 `cacheNames + key` 组合**,与内部是否调用 `findByEmail` 无关。 --- ### 2. **为什么能复用 `findByEmail` 的缓存?** #### (1)缓存存储结构示例 | 缓存键 | 缓存值 | |---------------------------|----------------------| | `userByEmail::a@test.com` | `List<GeneralUser>` | #### (2)调用 `findOneByEmail("a@test.com")` 时的实际步骤: 1. **生成缓存键**: AOP 层根据 `cacheNames="userByEmail"` 和 `key="#email"` 生成键 `userByEmail::a@test.com`。 2. **检查缓存**: 发现该键已存在(由之前的 `findByEmail` 调用写入),直接返回缓存的 `List<GeneralUser>`。 3. **执行方法逻辑**: 对返回的列表调用 `stream().findFirst()`,最终返回 `Optional<GeneralUser>`。 #### (3)核心逻辑: - **AOP 拦截发生在方法调用前**,此时尚未执行 `findByEmail(email)`。 - 只要缓存命中,`findOneByEmail` 的方法体根本不会执行(除非设置 `unless` 条件)。 --- ### 3. **通过字节码验证代理行为** 使用 **Arthas** 或 **Spring Debug 日志** 可观察到: ```java // 开启Spring Debug日志 logging.level.org.springframework.cache=DEBUG // 调用 findOneByEmail 时的日志输出: CacheInterceptor: Cache hit for key 'userByEmail::a@test.com' // 不会出现 "Executing findByEmail" 日志 ``` --- ### 4. **与普通 AOP 的区别** | 特性 | 常规 AOP(如 @Transactional) | Spring Cache AOP | |---------------------|------------------------------------|--------------------------------------| | **拦截时机** | 方法执行前后 | 方法执行前(缓存命中时跳过方法体) | | **依赖内部逻辑** | 是(需知道方法是否抛异常等) | 否(只依赖输入参数和返回类型) | | **数据传递** | 可修改参数/返回值 | 仅读取参数和返回值 | --- ### 5. **特殊场景的应对** #### 问题:若 `findOneByEmail` 需要独立逻辑怎么办? ```java @Cacheable(cacheNames = "userByEmail", key = "#email") public Optional<GeneralUser> findOneByEmail(String email) { // 直接查数据库,不调用 findByEmail return cassandraRepo.findFirstByEmail(email); } ``` 此时: - 缓存的值类型变为 `Optional<GeneralUser>`(需同步更新 `findByEmail` 的缓存策略)。 - 需用 `@CacheEvict` 在数据变更时清理缓存。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值