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 表达式官方文档
相关推荐
Data跳动5 小时前
Spark内存都消耗在哪里了?
大数据·分布式·spark
Java程序之猿6 小时前
微服务分布式(一、项目初始化)
分布式·微服务·架构
来一杯龙舌兰7 小时前
【RabbitMQ】RabbitMQ保证消息不丢失的N种策略的思想总结
分布式·rabbitmq·ruby·持久化·ack·消息确认
节点。csn8 小时前
Hadoop yarn安装
大数据·hadoop·分布式
NiNg_1_23410 小时前
基于Hadoop的数据清洗
大数据·hadoop·分布式
隔着天花板看星星11 小时前
Spark-Streaming集成Kafka
大数据·分布式·中间件·spark·kafka
技术路上的苦行僧15 小时前
分布式专题(8)之MongoDB存储原理&多文档事务详解
数据库·分布式·mongodb
龙哥·三年风水15 小时前
workman服务端开发模式-应用开发-后端api推送修改二
分布式·gateway·php
小小工匠16 小时前
分布式协同 - 分布式事务_2PC & 3PC解决方案
分布式·分布式事务·2pc·3pc
闯闯的日常分享18 小时前
分布式锁的原理分析
分布式