Spring 之 @Cacheable 缓存使用教程

1、@Cacheable 指定使用缓存

定义个 Controller ,在方法上加上注解 @Cacheable,配置要使用哪些缓存,比如 myMapCache 表示一级缓存是 Map,myRedisCache 表示二级缓存是 Redis。并配置缓存 key。 key 由 SPEL 表达式组成,Spring 可以动态解析生成缓存 key。

提示:myMapCache、myRedisCache 均为注入 Spring 缓存的名称

复制代码
@RestController
public class CachingAnnoController {

	@Cacheable(cacheNames = {"myMapCache","myRedisCache"},key = "'test_'+#key")
	@RequestMapping("/cachingAnno")
	public String cachingAnno(String key,String value){
		System.out.println("查询数据库值 = " + value);
		return "value";
	}
}

2、@EnableCaching 开启缓存功能

用过 @Cacheable 注解的都知道,要开启缓存功能必须通过 @EnableCaching 注解。代码如下:

复制代码
@ComponentScan(value = "com.gwm")
@Configuration
@EnableCaching
public class SpringConfig {

}

3、引入具体要使用的缓存---实现 Cache 接口

上述 @EnableCaching、@Cacheable 都是 Spring 提供的基本缓存框架,而具体的缓存需要自己引入。比如现在自己定义两个缓存:MyMapCache 类 、MyRedisCache 类。然后实现接口方法。

Spring 提供 Cache 作为标准化接口,具体的实现 Spring 不管,你自己去实现。但是 Spring 也是有几个默认实现的,比如:CaffeineCache、EhCacheCache、ConcurrentMapCache、JCacheCache 等。

复制代码
public class MyMapCache implements Cache {

	public static final Map<Object, Object> map = new ConcurrentHashMap<>();

	private String cacheName;

	public MyMapCache(String cacheName) {
		this.cacheName = cacheName;
	}

	@Override
	public String getName() {
		return cacheName;
	}

	@Override
	public Object getNativeCache() {
		return null;
	}

	@Override
	public ValueWrapper get(Object key) {
		System.out.println(">>>>>>我是 MyMapCache 缓存中的 get() 方法");
		Object o = map.get(key);
		if (Objects.nonNull(o)) {
			return new SimpleValueWrapper(o);
		}
		return null;
	}

	@Override
	public <T> T get(Object key, Class<T> type) {

		return (T)map.get(key);
	}

	@Override
	public <T> T get(Object key, Callable<T> valueLoader) {
		return (T)map.get(key);
	}

	@Override
	public void put(Object key, Object value) {
		System.out.println(">>>>>>我是 MyMapCache 缓存中的 put() 方法");
		map.put(key, value);
	}

	@Override
	public void evict(Object key) {
		map.remove(key);
	}

	@Override
	public void clear() {
		map.clear();
	}
}

MyRedisCache 类实现,代码如下:

复制代码
public class MyRedisCache implements Cache {

	private String cacheName;
	private RedisTemplate<Object,Object> redisTemplate;

	public MyRedisCache(String cacheName, RedisTemplate<Object, Object> redisTemplate) {
		this.cacheName = cacheName;
		this.redisTemplate = redisTemplate;
	}

	@Override
	public String getName() {
		return cacheName;
	}

	@Override
	public Object getNativeCache() {
		return this;
	}

	@Override
	public ValueWrapper get(Object key) {
		System.out.println(">>>>>>我是 MyRedisCache 缓存中的 get() 方法");
		Object o = redisTemplate.opsForValue().get(key);
		if (Objects.nonNull(o)) {
			return new SimpleValueWrapper(o);
		}

		return null;
	}

	@Override
	public <T> T get(Object key, Class<T> type) {
		return null;
	}

	@Override
	public <T> T get(Object key, Callable<T> valueLoader) {
		return null;
	}

	@Override
	public void put(Object key, Object value) {
		System.out.println(">>>>>>我是 MyRedisCache 缓存中的 put() 方法");
		redisTemplate.opsForValue().set(key,value);
		redisTemplate.expire(key, Duration.ofMillis(3000));
	}

	@Override
	public void evict(Object key) {
		redisTemplate.delete(key);
	}

	@Override
	public void clear() {
		redisTemplate.execute((RedisCallback<Object>) conn->{

			// 清空所有缓存数据,要格外注意这个操作,很危险
			conn.flushDb();

			return ">>>>>>flush db success!";
		});
	}
}

4、缓存管理类---实现 CacheManager 接口

自定义缓存定义完之后,光摆在这里肯定是不能起作用的,还需要借助 Spring 标准化接口 CacheManager 类来把缓存加入到缓存切面逻辑中。

比如实现 MyMapCache 缓存管理类 MyMapCacheManager,代码如下:

复制代码
public class MyMapCacheManager implements CacheManager {

	@Override
	public Cache getCache(String name) {
		return new MyMapCache(name);
	}

	@Override
	public Collection<String> getCacheNames() {
		return Arrays.asList("myMapCache");
	}
}

实现 MyRedisCache 缓存管理类 MyRedisCacheManager,代码如下:

复制代码
public class MyRedisCacheManager implements CacheManager {

	@Override
	public Cache getCache(String name) {
		return new MyRedisCache(name,redisTemplate);
	}

	@Override
	public Collection<String> getCacheNames() {
		return Arrays.asList("myRedisCache");
	}
}

然后在配置入口类中通过 @Bean 引用具体的缓存。代码如下:

复制代码
@Configuration
public class MyRedisMainConfig {

	@Resource
	private RedisTemplate<Object,Object> redisTemplate;

	@Bean
	public MyMapCache myMapCache() {
		MyMapCache myMapCache = new MyMapCache("myMapCache");
		return myMapCache;
	}
	
	@Bean
	public MyRedisCache myRedisCache() {
		MyRedisCache myRedisCache = new MyRedisCache("myRedisCache",redisTemplate);

		return myRedisCache;
	}
	
	@Bean
	public MyRedisCacheManager cacheManager() {
		MyRedisCacheManager redisCacheManager = new MyRedisCacheManager();
		return redisCacheManager;
	}
	
	// @Bean 
	public MyMapCacheManager cacheManager() {
		MyRedisCacheManager redisCacheManager = new MyRedisCacheManager();
		return redisCacheManager;
	}
	
}

但是发现使用具体的管理类引入缓存,只能引入对应的缓存。比如 MyRedisCacheManager 管理类就只能引入 MyRedisCache 缓存,不能引入 MyMapCache。所以这是个弊端。如果想要使用双缓存,那怎么办呢?

5、双缓存使用---实现 AbstractCacheManager 抽象类

该抽象类提供 loadCaches() 方法,可以获取到所有 Cache 接口实现类。所以这里能够获取到所有缓存。那么肯定是可以使用双缓存。比如 MySimpleCacheManager 类实现,代码如下:

复制代码
public class MySimpleCacheManager extends AbstractCacheManager implements ApplicationContextAware {

	private static final List<Cache> list = new ArrayList<>();

	private ApplicationContext context;
	/**
	 * 直接实现 AbstractCacheManager 抽象类的钩子方法,该类已经写好模版方法
	 * 当执行的时候,如果 MyGuavaCacheManager 管理类 @Bean 的话,就会勾到这个方法逻辑
	 * @return
	 */
	@Override
	protected Collection<? extends Cache> loadCaches() {
		return list;
	}

	@Override
	public void afterPropertiesSet() {
		Map<String, Cache> beansOfType = context.getBeansOfType(Cache.class);
		list.addAll(beansOfType.values());
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
		this.context = applicationContext;
	}
}

然后再配置入口类,代码如下:

复制代码
@Configuration
public class MyRedisMainConfig {

	@Resource
	private RedisTemplate<Object,Object> redisTemplate;

	@Bean
	public MyMapCache myMapCache() {
		MyMapCache myMapCache = new MyMapCache("myMapCache");
		return myMapCache;
	}
	
	@Bean
	public MyRedisCache myRedisCache() {
		MyRedisCache myRedisCache = new MyRedisCache("myRedisCache",redisTemplate);
		return myRedisCache;
	}
	
	@Bean
	public MySimpleCacheManager cacheManager(@Qualifier("myMapCache") MyMapCache myMapCache,
												 @Qualifier("myRedisCache") MyRedisCache myRedisCache,
												 @Qualifier("myGuavaCache") MyGuavaCache myGuavaCache) {
												 
		SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
		simpleCacheManager.setCaches(Arrays.asList(myMapCache,myRedisCache,myGuavaCache));
		return simpleCacheManager;
	}
}

最终通过注入一个缓存管理类 MySimpleCacheManager 就可以使用到三个缓存。美滋滋。最终要使用,就通过 @Cacheable(cacheNames = {"myMapCache","myRedisCache"},key = "'test_'+#key") 即可使用 myMapCache、myRedisCache 缓存。如果还需要使用 myGuavaCache,直接往数组中添加即可。

6、 RedisCacheManager 内置类

在使用 Redis 缓存时,Spring 早已对其包装完成,只需要引入 spring-data-redis 包即可。我们不需要写 MyRedisCacheManager 类,Spring 早就提供 RedisCacheManager 类管理 Redis 缓存 。所以你只需能够连接好 Redis 。然后通过 @Bean 将 RedisCacheManager 类注入即可使用 Redis 缓存。

相关推荐
雾月557 分钟前
LeetCode 1292 元素和小于等于阈值的正方形的最大边长
java·数据结构·算法·leetcode·职场和发展
24k小善1 小时前
Flink TaskManager详解
java·大数据·flink·云计算
想不明白的过度思考者1 小时前
Java从入门到“放弃”(精通)之旅——JavaSE终篇(异常)
java·开发语言
.生产的驴2 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
Pasregret2 小时前
多级缓存架构深度解析:从设计原理到生产实践
缓存·架构
猿周LV2 小时前
JMeter 安装及使用 [软件测试工具]
java·测试工具·jmeter·单元测试·压力测试
晨集2 小时前
Uni-App 多端电子合同开源项目介绍
java·spring boot·uni-app·电子合同
时间之城2 小时前
笔记:记一次使用EasyExcel重写convertToExcelData方法无法读取@ExcelDictFormat注解的问题(已解决)
java·spring boot·笔记·spring·excel
椰羊~王小美2 小时前
LeetCode -- Flora -- edit 2025-04-25
java·开发语言
凯酱2 小时前
MyBatis-Plus分页插件的使用
java·tomcat·mybatis