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

相关推荐
启山智软10 分钟前
【中大企业选择源码部署商城系统】
java·spring·商城开发
我真的是大笨蛋12 分钟前
深度解析InnoDB如何保障Buffer与磁盘数据一致性
java·数据库·sql·mysql·性能优化
怪兽源码41 分钟前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
恒悦sunsite1 小时前
Redis之配置只读账号
java·redis·bootstrap
梦里小白龙1 小时前
java 通过Minio上传文件
java·开发语言
人道领域1 小时前
javaWeb从入门到进阶(SpringBoot事务管理及AOP)
java·数据库·mysql
sheji52611 小时前
JSP基于信息安全的读书网站79f9s--程序+源码+数据库+调试部署+开发环境
java·开发语言·数据库·算法
毕设源码-邱学长1 小时前
【开题答辩全过程】以 基于Java Web的电子商务网站的用户行为分析与个性化推荐系统为例,包含答辩的问题和答案
java·开发语言
摇滚侠2 小时前
Java项目教程《尚庭公寓》java项目从开发到部署,技术储备,MybatisPlus、MybatisX
java·开发语言
€8112 小时前
Java入门级教程24——Vert.x的学习
java·开发语言·学习·thymeleaf·数据库操作·vert.x的路由处理机制·datadex实战