Spring缓存注解浅析及实践
一 背景
缓存是我们日常开发常被使用的技术栈,一般用来降低数据库读取压力,提升系统查询性能。使用缓存的数据一般为不常变化且使用率很高的数据,比如:配置信息、商品信息、用户信息等。我们一般的做法:第一次从数据库中读取数据,然后放到缓存中并设置缓存超期时间,缓存超期之后再从数据库从新读取,如果涉及到更新和删除数据也要同步缓存,这样才能解决缓存数据一致性问题,但我们常规的做法一般是使用缓存的put、get等命令把写入和读取缓存的代码写在方法体内部,这样缓存相关的操作代码就会耦合在业务代码里。
能不能加个缓存注解就能把缓存的的问题给解决了呢?常规的做法是自己定义一个缓存注解,使用AOP的机制来实现缓存读写和同步,但实际上我们做这一步是多余的,因为Spring本身就提供了强大的缓存注解功能,我们何必再重复造轮子呢。下面将简单介绍下Spring的几个关键缓存注解及如何使用它们来实现缓存读写、更新和删除。
二 Spring几个关键缓存注解介绍
下面简单介绍几个Spring提供的核心缓存注解:@EnableCaching,@Cacheable,@CachePut,@CacheEvict ,如下:
注解名称 | 简介 |
---|---|
@EnableCaching | 该注解用来开启缓存功能,配置类中需要加上这个注解,Spring才知道你需要缓存功能,另外其他和缓存相关的注解才会生效,Spring缓存注解也是通过AOP实现的,通过AOP来拦截需要使用缓存的方法,实现缓存功能。 |
@Cacheable | 该注解用来赋予缓存功能,它可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数访问该方法可以直接从缓存中获取结果,而不需要再次执行该方法。@Cacheable可以指定三个属性:value、key和condition。value:value和cacheNames属性作用一样,必须指定其中一个,表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。 key:缓存以key->value的形式存储,key属性指定缓存内容对应的key,key属性支持SpEL表达式;当我们没有指定该属性时,Spring将使用默认策略生成key。condition:用来控制缓存的使用条件,condition属性默认为true,其值是通过SpEL表达式来指定的,当为true时表示先尝试从缓存中获取;若缓存中不存在则执行方法并将方法返回值存入缓存;当为false时不走缓存直接执行方法,并且返回结果也不会存入缓存。 |
@CachePut | 该注解用来将结果放入缓存,该注解的用法跟@Cacheable类似,区别如下:@CachePut:这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中。 @Cacheable:当重复使用相同参数调用方法的时候,方法本身不会被调用执行,即方法本身被略过了,取而代之的是方法的结果直接从缓存中找到并返回了。 所以,@CachePut一般被用于缓存的更新同步,确保缓存数据一致性。 |
@CacheEvict | 该注解用来清除缓存,如果标注在方法上则目标方法被调用时会清除指定的缓存,@CacheEvict一般用于数据删除时同时删除缓存,确保缓存数据一致性。 |
三 工程实践
3.1 引入依赖
要在springboot中使用缓存,重点要引入依赖:spring-boot-starter-data-redis
<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>
<groupId>org.example</groupId>
<artifactId>spring-cache</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-cache</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.boot.version>2.3.8.RELEASE</spring.boot.version>
<slf4j-api.version>1.7.29</slf4j-api.version>
<log4j-api.version>2.3</log4j-api.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j-api.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
3.2 核心代码
首先,要创建缓存配置类,配置类中需要定义一个bean:缓存管理器,类型为CacheManager;另外两个配置:cacheEnable为true开启缓存,false为关闭缓存,cacheTtl为统一的缓存超时时间。
package com.java.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.support.NoOpCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.time.Duration;
/**
* redis缓存配置类
*
* @author jiangzhaojing
* @date 2024-11-29 15:01:12
*/
public class RedisCacheConfig {
private Boolean cacheEnable;
private Long cacheTtl;
private StringRedisTemplate redisTemplate;
/**
* 缓存管理bean注入
*
* @param redisConnectionFactory
* @return
*/
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
if (cacheEnable) {
RedisCacheConfiguration config = instanceConfig();
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
return new NoOpCacheManager();
}
/**
* 实例配置
*
* @return
*/
private RedisCacheConfiguration instanceConfig() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(cacheTtl))
.disableCachingNullValues();
}
}
其次,创建测试需要实体类,需要注意是,该实体类必须实现Serializable,否则会出现序列化异常。
package com.java.demo.model;
import java.io.Serializable;
/**
* 用户实体类
*
* @author jiangzhaojing
* @date 2024-11-29 15:01:12
* 用户相关属性
*/
public class User implements Serializable {
private String userId;
private String userName;
public User() {
}
public User(String userId, String userName) {
this.userId = userId;
this.userName = userName;
}
public String toString() {
return String.format("[userId:%s,userName:%s]", userId, userName);
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
然后,创建接口服务及实现类,并在实现类方法上增加缓存注解,如下:
package com.java.demo.service;
import com.java.demo.model.User;
/**
* 用户相关服务
*
* @author jiangzhaojing
* @date 2024-11-29 15:01:12
*/
public interface UserService {
/**
* 根据用户ID获取用户
*
* @param userId
* @return
*/
User getUserById(String userId);
/**
* 更新用户
*
* @param user
* @return
*/
User updateUser(User user);
/**
* 删除用户
*
* @param userId
*/
void deleteUser(String userId);
}
package com.java.demo.service.impl;
import com.java.demo.model.User;
import com.java.demo.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
/**
* 用户相关服务实现
*
* @author jiangzhaojing
* @date 2024-11-29 15:01:12
*/
public class UserServiceImpl implements UserService {
private final static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
/**
* 根据用户ID获取用户
*
* @param userId
* @return
*/
"users", key = "#userId") (cacheNames =
public User getUserById(String userId) {
logger.info("调用了方法[getUserById],入参:{}", userId);
//正常下面应该从数据库中读取
return new User("123", "li lei");
}
/**
* 更新用户
*
* @param user
* @return
*/
"users", key = "#user.userId") (cacheNames =
public User updateUser(User user) {
logger.info("调用了方法[updateUser],入参:{}", user);
return user;
}
/**
* 更新用户
*
* @param userId
* @return
*/
"users", key = "#userId") (cacheNames =
public void deleteUser(String userId) {
logger.info("调用了方法[deleteUser],入参:{}", userId);
}
}
然后,写一个应用的启动类
package com.java.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
"com.java.demo") (
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
最后,配置文件配置缓存相关配置项,其中spring.redis.host,spring.redis.password,spring.redis.port三项根据实际配置填写
spring.redis.host=//redis地址
spring.redis.database=0
spring.redis.port=//redis端口
spring.redis.password=//redis密码
spring.redis.timeout=5000
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=1
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=3000
cache.enable=true
cache.ttl=300
3.3 测试用例
package com.java.demo;
import com.java.demo.model.User;
import com.java.demo.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* 用户相关服务
*
* @author jiangzhaojing
* @date 2024-11-29 15:01:12
*/
public class CacheTest {
private final static Logger logger = LoggerFactory.getLogger(CacheTest.class);
private UserService userService;
public void testCache() {
//第一次读取缓存为空
logger.info("1.user:{}", userService.getUserById("123"));
//第二次直接从缓存读取
logger.info("2.user:{}", userService.getUserById("123"));
//更新缓存
userService.updateUser(new User("123", "zhang hua"));
//第三次直接从缓存读取
logger.info("3.user:{}", userService.getUserById("123"));
//删除缓存
userService.deleteUser("123");
logger.info("test finish!");
}
}
第一次读取,缓存还没有则直接进入方法体并写入缓存,如下图:
四 总结
从上面的解析和实践中可以看到使用Spring提供的@EnableCaching注解可以方便进行缓存的处理,避免缓存处理逻辑与业务代码耦合,让代码更优雅,从一定程度上提升了开发效率。但细心的同学会发现一个问题:@EnableCaching注解并未提供缓存超期的属性,所以我们无法通过@EnableCaching设置缓存超时时间,只能通过CacheManager设置一个统一的缓存超期时间。通过@EnableCaching源码我们也能发现并无缓存超期属性,如下:
package org.springframework.cache.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
public Cacheable {
String[] value() default {};
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
boolean sync() default false;
}
至于Spring不提供这个属性原因,可能是基于框架的扩展性和通用性方面的考虑,不过Spring的强大之处就在于它是可以扩展的,预留了很多扩展点等待我们去实现,本文因篇幅有限不在本篇讨论如何扩展实现缓存超时时间的问题,留在后面的文章继续探讨。以上的分析讨论及代码难免有错误之处,敬请同学们指正!
五 源码
Spring缓存注解工程实践相关源码: https://3.cn/10h-Vk1KT
本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
一键部署 Dify + MCP Server,高效开发 AI 智能体应用
作者:黄刚(泽尘) |孙小涵(子照)|赵庆杰(卢令)|王霄霄(丛霄) 前言 全球 AI 开发者正在见证一场 "AI 智能体革命"。 从 2024 年 Dify.ai 发布开源框架 Dify以来,到全球开发者基于其快速构建的智能客服、企业知识库、AI 营销助手等场景应用,GitHub 上已有超过 2.3 万个 AI 应用项目诞生。 如今,MCP 的协议也异军突起,大量基于 MCP 协议的 MCP Server 应用涌上市场,它解决了各个模块之间交互不统一的问题,让一切现有传统应用就像 USB 接口一样,随意在 AI 智能体上插拔使用。 那如何快速的基于 MCP Server 构建出一个 AI 应用?我们大胆的猜测,Dify + MCP Server 将是一个完美的结合,因为 Dify 有强大的编排能力,再加上 MCP Server 标准的接口协议,在 Dify 平台上,只需要进行简单的拖拽就可以快速的构建一个生产级别的 AI 智能体应用。 本次我们选择了阿里云 Serverless 应用引擎(下面简称 SAE)来部署 Dify、MCP 应用,并且期望通过 Dify 快速编排 MCP Se...
- 下一篇
做Data+AI的长期主义者,加速全球化战略布局
在Data与AI深度融合的新纪元,唯有秉持长期主义方能真正释放数智化的深层价值。2025年是人工智能从技术爆发转向规模化落地的关键节点,也是标志着袋鼠云即将迎来十周年的重要里程碑。2025年4月16日,袋鼠云成功举办了"做Data+AI的长期主义者------2025年袋鼠云春季数智发布会",从智能分析决策平台到AI大模型应用,从数字孪生中枢到AI增强型数字世界,勾勒出企业数智化转型的进化图谱,真正实现AI赋能企业业务,让数据产生价值! 同时,在本次春季发布会上,袋鼠云重磅发布了《2025数字孪生与智能算法白皮书》,本书聚焦数字孪生与智能算法的深度协同创新,从技术和实践两个方面传递Data+AI的价值,系统构建"理论-技术-实践"三位一体的数字孪生智能体系,涵盖了能源、文旅、物流、港口等多行业实际项目应用场景的可复用方法论,为企业绘制了从"可视化孪生"向"智能孪生"跃迁的发展蓝图。 创业十年 • 初心如磐,智启未来 袋鼠云创始人、董事长陈吉平在开场中提到,创业十年来,袋鼠云始终以"让数据产生价值"为使命,从初创企业发展为涵盖AI、数据中台、智能运维、数字孪生等领域的数智化服务商。袋鼠...
相关文章
文章评论
共有0条评论来说两句吧...