Springboot-Cache

发布时间 2023-08-07 09:59:00作者: primaryC

一. 缓存抽象

从 3.1 版本开始,Spring 框架提供了对现有 Spring 应用透明地添加缓存的支持。与 事务 支持类似,缓存抽象允许一致使用各种缓存解决方案,对代码的影响最小。

在 Spring Framework 4.1 中,缓存抽象得到了极大的扩展,支持 JSR-107 注解 和更多的自定义选项。

缓存服务是一个抽象(而不是缓存实现),需要使用实际的存储来存储缓存数据—​也就是说,这个抽象让你不必编写缓存逻辑,但不提供实际的数据存储。这种抽象由 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口具体化。

Spring 提供了 一些该抽象的实现: 基于 JDK java.util.concurrent.ConcurrentMap 的缓存、Gemfire 缓存、 Caffeine 以及符合JSR-107标准的缓存(如 Ehcache 3.x)。

中文文档:https://springdoc.cn/spring/integration.html#cache

二. 相关 API

1. Cache

表示一个缓存,缓存内容的单位。有以下实现:

以 ConcurrentMapCache 为例:

public class ConcurrentMapCache extends AbstractValueAdaptingCache {
    
    //缓存的名字,不知道有啥用
	private final String name;
    
    //使用 ConcurrentMap 来存储缓存,每个键值对表示一个缓存。其中键对应 @Cacheable(key="") Key 的值,值就是缓存的内容
	private final ConcurrentMap<Object, Object> store;

	@Nullable
	private final SerializationDelegate serialization;

    ...
}

相当于多个缓存键值对的结合,其他实现类没看,应该也差不多是这样吧。。。

2. CacheManager

  常规的SpringBoot已经为我们自动配置了EhCache、Collection、Guava、ConcurrentMap等缓存,默认使用ConcurrentMapCacheManager。SpringBoot的application.properties配置文件,使用spring.cache前缀的属性进行配置。

缓存管理器,有以下实现:

同样以 ConcurrentMapCacheManager 为例:

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
    
    //内部持有一个 concurrentMap,这个 map 的键值对 key-value,key 就是每个缓存的名字,value 则是每个上述的 Cache
	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

	private boolean dynamic = true;

	private boolean allowNullValues = true;

	private boolean storeByValue = false;

  ...
}

每个 Spring 可以配置多个 CacheManager,但只能自动装配一个,否则会报错。

//这种就会出现异常,无法启动项目
@Configuration
public class CacheConfig {

    @Bean
    public CacheManager concurrentMapCacheManager(){
        return new ConcurrentMapCacheManager("aaa", "bbb");
    }

    @Bean
    public CacheManager simpleCacheManager(){
        return new SimpleCacheManager();
    }
}

需要使用 @Bean(autowireCandidate = false) 来禁止 bean 自动装配,然后要使用禁止自动装配的 CacheMange,就需要在 @Cacheable 上指定 cacheManger 属性

3. Cache 和 CacheManager 的关系

每个 Cache 好比一组缓存内容,内部是一个 map 来维护。而 CacheManager 则管理了很多个 Cache,通过 CacheManage 的 cacheName 或 value 指定使用哪个 Cache,而 key 指定 Cache 中的哪个键值对。

三. 基于声明式注解缓存

1. @Cacheable

使用 @Cacheable 来划分可缓存的方法—​也就是那些结果被存储在缓存中的方法,这样,在后续的调用中(使用相同的参数),缓存中的值就会被返回,而无需实际调用该方法。每次调用该方法时,都会检查缓存,看是否已经运行过该调用,而不必重复。虽然在大多数情况下,只有一个缓存被声明,但注解允许指定多个名字,这样就可以使用多个缓存了。在这种情况下,每个缓存在调用方法之前都会被检查—​如果至少有一个缓存被命中,相关的值会被返回。

所有其他不包含该值的缓存也会被更新,即使该缓存方法没有被实际调用。

1. cacheManager

指明某个固定的 CacheManager

2. cacheResolver

该属性,用来指定缓存管理器。使用配置同 cacheManager 类似,可自行百度。(cacheManager指定管理器/cacheResolver指定解析器 它俩也是二选一使用),不深究

3. value

缓存 Cache 的名称,支持 spel 表达式。

4. cacheNames

同 value

5. key

缓存 Cache 中的键值,默认规则:

  • 如果请求没有参数:key=new SimpleKey();
  • 如果请求有一个参数:key=参数的值
  • 如果请求有多个参数:key=newSimpleKey(params);

key值的编写,可以使用 SpEL 表达式的方式来编写:

名字位置描述示例
methodNameroot object当前被调用的方法名#root.method.name
methodroot object当前被调用的方法#root.methodName
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类#root.targetClass
argsroot object当前被调用的方法的参数列表#root.args[0]
cachesroot object当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”,“cache2”})),则有两个cache#root.caches[0].name
argument nameevaluation context方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引;#id、#p0、#a0
resultevaluation context方法执行后的返回值(仅当方法执行之后的判断有效,如’unless’、'cache put’的表达式 'cacheevict’的表达式beforeInvocation=false)#result

6. keyGenerator

key 的生成器。如果觉得通过参数的方式来指定比较麻烦,我们可以自己指定 key 的生成器的组件 id。key/keyGenerator属性:二选一使用。不深究

7. condition

条件判断属性,用来指定符合指定的条件下才可以缓存。也可以通过 SpEL 表达式进行设置。这个配置规则和 key 属性表格中的配置规则是相同的。

8. unless

unless属性,意为"除非"的意思。即只有 unless 指定的条件为 true 时,方法的返回值才不会被缓存

9. sync

该属性用来指定是否使用异步模式,该属性默认值为 false,默认为同步模式。异步模式指定 sync = true 即可,异步模式下 unless 属性不可用。

2. @CachePut

当需要更新缓存而不干扰方法的执行时,你可以使用 @CachePut 注解。也就是说,该方法总是被调用,其结果被放入缓存(根据 @CachePut 选项)。它支持与 @Cacheable 相同的选项,应该用于缓存的填充而不是方法流的优化。

最好不要和 @Cacheable 一起用

3. @CacheEvict

缓存抽象不仅允许缓存存储,而且还允许驱逐。这个过程对于从缓存中移除陈旧或未使用的数据很有用。与 @Cacheable 相反,@CacheEvict 划分了执行缓存驱逐的方法(也就是说,作为从缓存中移除数据的触发器的方法)。与它的兄弟姐妹类似,@CacheEvict 需要指定一个或多个受行动影响的缓存,允许指定一个自定义的缓存和 key 解析或条件,并具有一个额外的参数(allEntries),表明是否需要执行整个缓存的驱逐,而不仅仅是一个条目的驱逐(基于 key)

1. allEntries

使用 allEntries = true 属性来驱逐缓存中的所有条目

2. beforeInvocation

beforeInvocation=true 导致驱逐总是在方法被调用之前发生。这在驱逐不需要与方法结果相联系的情况下很有用。

4. @Caching

有时,同一类型的多个注解(如 @CacheEvict 或 @CachePut)需要被指定—​例如,因为不同缓存的 condition 或 key 表达式是不同的。@Caching 允许在同一个方法上使用多个嵌套的 @Cacheable、@CachePut 和 @CacheEvict 注解

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

5. @CacheConfig

@CacheConfig 是一个类级注解,它允许共享缓存名称、自定义 KeyGenerator、自定义 CacheManager 和自定义 CacheResolver。把这个注解放在类上并不开启任何缓存操作。

一个操作级别的自定义总是覆盖 @CacheConfig 上的自定义设置。因此,这为每个缓存操作提供了三个层次的自定义:

  • 全局配置,可用于 CacheManager、KeyGenerator。
  • 在类级别,使用 @CacheConfig。
  • 在操作级别上。

6. @EnableCaching

启用缓存,用在配置类或启动类上

四. 使用示例

1. 配置缓存配置类

配置两个 cacheManager,只能装配一个,所以其他的 bean 使用 autowireCandidate=false 进制自动装配。因为这里配置的是 concurrentMapCacheManager 和 simpleCacheManager,所以不用额外依赖。

@Configuration
public class CacheConfig {

    @Bean(autowireCandidate = false)
    public CacheManager concurrentMapCacheManager(){
        return new ConcurrentMapCacheManager("aaa", "bbb");
    }

    @Bean
    public CacheManager simpleCacheManager(){
        return new SimpleCacheManager();
    }
}

2. 控制层

@RestController
@RequestMapping("cache")
public class CacheController {

    @Autowired
    private CacheService cacheService;

    @Autowired
    private CacheManager cacheManager;

    @RequestMapping("/test")
    public void test(){
        System.out.println( cacheService.addNum(1, 2) );
    }

    @RequestMapping("/s")
    public void test1(){
        System.out.println( cacheManager );
    }
}

3. 业务层

使用 @Cacheable 来配置缓存

@Service
@Slf4j
public class CacheService {

    @Cacheable(cacheNames = "aaa", key = "'yqkey'", cacheManager = "concurrentMapCacheManager")
    public int addNum(int num1, int num2){

        log.info("参数:num1={},num2={}", num1, num2);

       //模拟业务需要的时间
        try {
            Thread.sleep(1_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return num1 + num2;
    }
}

4. 启动类

@SpringBootApplication
@EnableCaching
public class CacheMain {
    public static void main(String[] args) {
        SpringApplication.run(CacheMain.class, args);
    }
}