{T}

缓存模块

缓存存在于工作的方方面面,小到 CPU 级别,大到项目级别,都会用到缓存。当然了,这也是面试的一个高频的重点区域

从原理入手,带大家从概念上以及现有的设计规范上认识缓存的设计理念

从实践出发,站在Spring的肩膀上去编排我们自己的缓存模块

根据缓存框架,将常用的缓存方案插件式的集成到项目中来

JSR 107

JSR107(Java Specification Requests ) 规范。就是关于如何使用缓存的规范,是 java 提供的一个接口规范,类似于 JDBC 规范,没有具体的实现,具体的实现就是 ehcache 等这些缓存解决方案

JSR107 核心接口

Java Caching USR-107) 定义了5个核心接口,分别是CachingProvider、CacheManager、Cache、Entry和Expiry.

  1. CachingProvider(缓存提供者):创建、配置、获取、管理和控制多个 CacheManager。
  2. CacheManager(緩存管理器):创建、配置、获取、管理和控制多个唯一命名的Cache,Cache存在于CacheManager的上下文中。一个 CacheManager 仅对应一个 CachingProvider
  3. Cache(缓存):是由 CacheManager 管理的,CacheManager 管理 Cache 的生命周期,Cache 存在于 CacheManager 的上下文中,是一个类似 map 的数据结构,并临时存储以 key 为索引的值。一个 Cache 仅被一个 CacheManager 所拥有
  4. Entry(缓存键值对):是一个存储在 Cache 中的 key-value 对
  5. Expiry(缓存时效):每一个存储在 Cache 中的条目都有一个定义的有效期。一旦超过这个时间,条目就自动过期,过期后,条目将不可以访问、更新和删除操作。缓存有效期可以通过 ExpiryPolicy 设置

使用 jsr-107需导入的依赖:

xml
<dependency>
  <groupid>javax.cache</groupid>
  <artifactid>cache-api</artifactid>
</dependency>

Spring 对 Cache 的抽象架构

Spring 对缓存的抽象主要包括以下几个核心接口和类:

  1. CacheManager: 这是 Spring 缓存管理器的核心接口,它负责管理多个 Cache 实例。每个 Cache 实例都代表一个缓存区域
  2. Cache: 这是 Spring 缓存区域的核心接口,它定义了缓存的基本操作,如查找、更新、插入和删除
  3. @Cacheable: 这是一个方法级别的注解,用于指示 Spring 在调用方法之前,首先从缓存中查找方法的结果。如果找到了结果,就返回缓存的值,否则调用方法,然后将结果放入缓存中
  4. @CachePut: 这是一个方法级别的注解,用于指示 Spring 在方法调用后,无论方法的结果如何,都将其放入缓存中
  5. @CacheEvict: 这是一个方法级别的注解,用于指示 Spring 在方法调用后,从缓存中删除指定的数据
  6. @Caching: 这是一个方法级别的注解,用于组合多个缓存注解
  7. @CacheConfig: 这是一个类级别的注解,用于共享一些常见的缓存设置

这些抽象结构使得 Spring 的缓存非常灵活,可以适应各种不同的缓存需求。同时,由于这些抽象结构都是接口或注解,因此可以很容易地扩展和定制

Spring 对于 Cache 的抽象架构

创建 cache 模块

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.xiaoye</groupId>
    <artifactId>framework</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>cache</artifactId>
  <packaging>pom</packaging>
  <modules>
    <module>cache-core</module>
    <module>cache-caffeine</module>
    <module>cache-redis</module>
  </modules>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

</project>

cache-caffeine 模块

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.xiaoye</groupId>
    <artifactId>cache</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>cache-caffeine</artifactId>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.xiaoye</groupId>
      <artifactId>cache-core</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
    </dependency>
    <dependency>
      <groupId>com.github.ben-manes.caffeine</groupId>
      <artifactId>caffeine</artifactId>
    </dependency>
  </dependencies>
</project>
framework/cache/cache-caffeine/src/main/java/com/xiaoye/cache/caffeine/config/CaffeineCacheProperties.java
package com.xiaoye.cache.caffeine.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * Caffeine Cache自定义配置属性类
 */
@Data
@Component
@ConfigurationProperties(prefix = "com.xiaoye.cache.caffeine")
public class CaffeineCacheProperties {

    /**
     * 缓存初始容量
     * com.xiaoye.cache.caffeine.init-cache-capacity
     */
    private Integer initCacheCapacity = 256;

    /**
     * 缓存最大容量,超过之后会按照 recently or very often(最近最少)策略进行缓存剔除
     * com.xiaoye.cache.caffeine.max-cache-capacity
     */
    private Long maxCacheCapacity = 10000L;

    /**
     * 是否允许空值 null 作为缓存的 value
     * com.xiaoye.cache.caffeine.allow-null-value
     */
    private Boolean allowNullValue = Boolean.TRUE;
}
framework/cache/cache-caffeine/src/main/java/com/xiaoye/cache/caffeine/config/CaffeineCacheConfig.java
package com.xiaoye.cache.caffeine.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.xiaoye.cache.core.constants.CacheConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;

@SpringBootConfiguration
@EnableCaching  // 开启缓存
@Slf4j
public class CaffeineCacheConfig {

    @Autowired
    private CaffeineCacheProperties properties;

    @Bean
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager(CacheConstants.Y_PAN_CACHE_NAME);

        cacheManager.setAllowNullValues(properties.getAllowNullValue());

        Caffeine<Object, Object> caffeineBuilder = Caffeine.newBuilder()
        .initialCapacity(properties.getInitCacheCapacity())
        .maximumSize(properties.getMaxCacheCapacity());

        cacheManager.setCaffeine(caffeineBuilder);

        log.info("the caffeine cache manager is loaded successfully!");

        return cacheManager;
    }
}

配置提示 framework/cache/cache-caffeine/src/main/resources/META-INF/additional-spring-configuration-metadata.json

json
{
  "properties": [
    {
      "name": "com.xiaoye.cache.caffeine.init-cache-capacity",
      "type": "java.lang.Integer",
      "description": "Caffeine 缓存的初始容量",
      "defaultValue": 256
    },
    {
      "name": "com.xiaoye.cache.caffeine.max-cache-capacity",
      "type": "java.lang.Long",
      "description": "Caffeine 缓存的最大容量",
      "defaultValue": "10000L"
    },
    {
      "name": "com.xiaoye.cache.caffeine.allow-null-value",
      "type": "java.lang.Boolean",
      "description": "Caffeine 缓存是否允许 null 作为 value 的属性",
      "defaultValue": "true"
    }
  ]
}

cache-redis 模块

版本差异(缓存模块 → 当前)

特性旧版当前
本地缓存Caffeine不变;Caffeine 3.x 支持 JDK 17/21
分布式缓存Redis 6.2.6Redis 7.x;客户端 Lettuce 6.x(spring-data-redis 3.x)
多级缓存Caffeine + Redis不变;注意缓存一致性(先更新 DB 再删缓存)
缓存注解@Cacheable 等不变;Boot 3.x 中 spring.cache.type 配置键稳定

缓存分层设计与一致性策略(Cache Aside 模式)不随版本变化;升级重点是 Redis 7.x 与 spring-data-redis 3.x 的兼容。