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 缓存。

相关推荐
java叶新东老师4 分钟前
idea提交时忽略.class、.iml文件和文件夹或目录的方法
java·开发语言
飞翔的佩奇10 分钟前
Java项目:基于SSM框架实现的社区团购管理系统【ssm+B/S架构+源码+数据库+毕业论文+答辩PPT+远程部署】
java·数据库·vue.js·毕业设计·mybatis·答辩ppt·社区团购
TDengine (老段)23 分钟前
TDengine 转化函数 TO_TIMESTAMP 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
Warren9827 分钟前
Java Collections工具类
java·开发语言·笔记·python·学习·oracle·硬件工程
java叶新东老师43 分钟前
CMakelists.txt 实现多级目录编译
java·服务器·数据库
_风不会停息1 小时前
JDK1.8升级 JDK21 实践踩坑
java
Java水解1 小时前
Spring AI模块化RAG架构解析:三阶段设计与实现详解
后端·spring
SimonKing1 小时前
一文搞定:SpringBoot集成语音识别模型FunASR
java·人工智能·后端
EumenidesJ1 小时前
Java常用日志框架介绍
java·log4j·logback·slf4j
zc-code1 小时前
HTTP性能优化实战:从协议到工具的全面加速指南
网络·网络协议·http·缓存·性能优化·html