Spring Cacheable 注解

Spring Cacheable 注解

在Spring框架中,缓存是一种提高应用程序性能的重要技术手段。@Cacheable注解是Spring Cache中最常用的注解之一,它用于将方法的返回值缓存起来,以便后续调用时直接从缓存中获取,而不是再次执行方法。本文将详细介绍@Cacheable注解的使用方法及其工作原理。

什么是 @Cacheable 注解

@Cacheable注解是Spring提供的一种缓存注解,它可以应用于方法上,用于标记该方法的返回值需要缓存。每次调用该方法时,Spring会首先检查缓存中是否存在该方法的返回值,如果存在则直接返回缓存中的值,否则执行该方法并将其返回值存入缓存中。

@Cacheable 注解的基本使用

引入依赖

在使用@Cacheable之前,需要在项目中引入相关的Spring Cache依赖。对于Spring Boot项目,可以在pom.xml中添加以下依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

配置缓存

接下来,需要在Spring Boot应用程序中启用缓存支持,并配置缓存管理器。例如,可以在application.properties中配置缓存:

properties 复制代码
spring.cache.type=caffeine
spring.cache.cache-names=exampleCache
spring.cache.caffeine.spec=maximumSize=100,expireAfterAccess=600s

在主应用程序类中启用缓存:

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

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

使用 @Cacheable 注解

在需要缓存的方法上使用@Cacheable注解,例如:

java 复制代码
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class ExampleService {

    @Cacheable("exampleCache")
    public String getData(String param) {
        // 模拟耗时操作
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Data for " + param;
    }
}

当第一次调用getData方法时,会执行实际的业务逻辑并将结果缓存起来。再次调用时,将直接从缓存中获取数据,而不再执行耗时的操作。

@Cacheable 注解的高级用法

指定缓存的Key

默认情况下,@Cacheable会使用方法参数作为缓存的Key。可以通过key属性指定自定义的Key:

java 复制代码
@Cacheable(value = "exampleCache", key = "#param")
public String getData(String param) {
    // ...
}

条件缓存

可以使用condition属性指定条件,满足条件时才进行缓存:

java 复制代码
@Cacheable(value = "exampleCache", condition = "#param.length() > 3")
public String getData(String param) {
    // ...
}

缓存同步

在并发环境中,可以使用sync属性启用缓存同步,防止缓存击穿:

java 复制代码
@Cacheable(value = "exampleCache", sync = true)
public String getData(String param) {
    // 模拟耗时操作
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "Data for " + param;
}

当多个线程同时请求缓存中没有的数据时,只有一个线程会执行方法,其他线程等待结果。这种机制能够有效避免多个线程同时执行相同的方法,从而减少资源消耗,提高系统性能。

例如,当大量并发请求同时查询某个缓存中没有的数据时,如果没有同步机制,每个请求都会触发一次方法执行,这不仅浪费资源,还可能导致数据库或其他底层服务过载。而启用sync属性后,只有一个请求会执行方法,其他请求等待结果,执行完毕后,结果会被缓存,后续请求直接从缓存中获取数据。

自定义的Key生成器keyGenerator

keyGenerator参数用于指定一个自定义的Key生成器,覆盖默认的Key生成方式。需要实现org.springframework.cache.interceptor.KeyGenerator接口,并在配置类或服务类中进行注册。

自定义Key生成器示例:

java 复制代码
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return method.getName() + "_" + Arrays.toString(params);
    }
}

在使用@Cacheable时指定自定义Key生成器:

java 复制代码
@Cacheable(value = "exampleCache", keyGenerator = "customKeyGenerator")
public String getData(String param) {
    // ...
}

详细介绍 cacheManager

cacheManager参数用于指定一个自定义的缓存管理器。如果没有指定,使用默认的缓存管理器。缓存管理器负责管理应用程序中的所有缓存实例。

自定义缓存管理器示例:

java 复制代码
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CacheConfig {

    @Bean
    public CacheManager customCacheManager() {
        return new ConcurrentMapCacheManager("exampleCache");
    }
}

在使用@Cacheable时指定自定义缓存管理器:

java 复制代码
@Cacheable(value = "exampleCache", cacheManager = "customCacheManager")
public String getData(String param) {
    // ...
}

自定义的缓存解析器cacheResolver

cacheResolver参数用于指定一个自定义的缓存解析器。需要实现org.springframework.cache.interceptor.CacheResolver接口,缓存解析器负责根据某些逻辑来解析和提供缓存实例。

自定义缓存解析器示例:

java 复制代码
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Collections;

@Component("customCacheResolver")
public class CustomCacheResolver implements CacheResolver {

    private final CacheManager cacheManager;

    public CustomCacheResolver(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Override
    public Collection<? extends Cache> resolveCaches(org.springframework.cache.interceptor.CacheOperationInvocationContext<?> context) {
        // 自定义缓存解析逻辑
        return Collections.singleton(cacheManager.getCache("exampleCache"));
    }
}

在使用@Cacheable时指定自定义缓存解析器:

java 复制代码
@Cacheable(value = "exampleCache", cacheResolver = "customCacheResolver")
public String getData(String param) {
    // ...
}

unless

unless参数是一个SpEL表达式,只有当表达式为false时才缓存方法的返回值。它的优先级高于condition,可以用于在某些条件下禁用缓存。

例如,可以在结果长度大于10时不缓存:

java 复制代码
@Cacheable(value = "exampleCache", unless = "#result.length() > 10")
public String getData(String param) {
    // ...
}

通过unless参数,可以在不改变方法逻辑的情况下,灵活控制缓存行为,避免不必要的缓存操作,提高系统性能。

缓存一致性问题

在使用缓存时,一个常见的问题是缓存一致性问题。当数据源(如数据库)中的数据发生变化时,缓存中的数据可能不会及时更新,导致缓存中的数据与实际数据不一致。为了解决这个问题,可以使用以下几种策略:

  1. 缓存失效策略:在更新或删除数据时,同时让相关缓存失效。
  2. 缓存刷新:定期刷新缓存,确保缓存中的数据是最新的。
  3. 消息队列:使用消息队列,在数据变化时通知缓存进行更新。

例如,在数据更新时手动清除缓存:

java 复制代码
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

@Service
public class ExampleService {

    @CacheEvict(value = "exampleCache", key = "#param")
    public void updateData(String param, String newData) {
        // 更新数据库中的数据
    }
}

参考链接

  1. Spring Cache 官方文档
  2. Spring Boot Cache 官方文档
  3. Caffeine 缓存库
  4. SpEL 表达式官方文档
相关推荐
是2的10次方啊1 小时前
微信公众号阅读量为什么会不一致?一文读懂分布式系统的那些事儿
分布式
Rancemy13 小时前
rabbitmq 03
java·分布式·rabbitmq
黄雪超16 小时前
Kafka——多线程开发消费者实例
大数据·分布式·kafka
阿里巴巴淘系技术团队官网博客16 小时前
面向互联网2C业务的分布式类Manus Java框架
java·开发语言·分布式
sniper_fandc17 小时前
RabbitMQ—HAProxy负载均衡
分布式·rabbitmq·负载均衡
你想知道什么?18 小时前
RabbitMQ简述
分布式·rabbitmq
sanggou1 天前
Zookeeper的分布式事务与原子性:深入解析与实践指南
分布式·zookeeper·云原生
月忆3641 天前
Etcd原理基础学习
分布式·golang
茉莉玫瑰花茶1 天前
服务端高并发分布式结构演进之路
分布式
IvanCodes1 天前
三、Spark 运行环境部署:全面掌握四种核心模式
大数据·分布式·spark