一、如何使用
网上各种教程比较多,简单介绍一下,不写那么详细。
1、添加Redis的依赖
org.springframework.boot
spring-boot-starter-data-redis
2、启动类中添加注解
@EnableCaching
3、配置文件中添加相关配置
缓存类型配置,使用本地缓存可以不配置或者配置为simple,使用Redis来缓存可以配置为redis
spring:
cache:
type: simple
redis相关配置
spring:
cache:
type: simple
redis:
host: localhost
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: 1
max-idle: 8
min-idle: 0
4、使用注解来配置需要缓存的方法
写了一个简单的例子,分别使用了@Cacheable注解、@CachePut注解、@CacheEvict注解。
注解中value是用来标识缓存名称的,和cacheName一样。可以理解为前缀,对一类的数据缓存进行分类。
注解中的key是缓存的唯一标识,支持SpEL。“#p0”就表示以第一个入参作为key,只要这个入参相同,就会先从缓存中获取数据,如果获取数据就直接返回缓存中的数据,否则就会执行该方法,并将方法返回值放入缓存。
@Override
@Cacheable(value = "cache_test", key = "#p0")
public int cacheResult(int key) {
LOGGER.info("调用缓存方法:cacheResult,缓存的key为:" + key);
Random random = new Random();
return random.nextInt(1000);
}
@Override
@CachePut(value = "cahce_test", key = "#p0")
public int updateCache(int key) {
LOGGER.info("调用更新缓存方法:updateCache,缓存的key为:" + key);
Random random = new Random();
return random.nextInt(1000);
}
@Override
@CacheEvict(beforeInvocation = true, value = "cahce_test", key = "#p0")
public void removeCache(int key) {
LOGGER.info("清除缓存,缓存的key为:" + key);
}
@Cacheable用来缓存数据,相同的key第一次会调用方法,然后将返回值缓存,后面相同的key不再调用方法直接从缓存中返回数据
@Cacheput会更新缓存数据,即使相同的key也会执行方法,然后将返回值更新到缓存
@CacheEvict会清除缓存,根据value和key的值清除对应缓存,beforeInvocation的值为true则在方法调用前就清除缓存,为false则在方法结束后清除缓存(如果方法抛异常则不会清除缓存)
二、Demo演示
1、演示@Cacheable的使用
代码如下,缓存方法中会生成随机数,便于演示。
@Resource
private ICacheDemoSV cacheDemoSV;
/**
* 演示使用代理的正常情况下,可以缓存数据
* @param key 输入的key
*/
@Override
public void demo1(int key) {
LOGGER.info("***************************");
LOGGER.info("方法:demo1");
LOGGER.info("时间为:" + new Date().toString());
LOGGER.info("输入值为:" + key);
int result = cacheDemoSV.cacheResult(key);
LOGGER.info("缓存方法的返回值为:" + result);
LOGGER.info("***************************");
}
然后我们多调用三次该方法,入参为均为1,在相同的key的情况下可以看到第一次调用了缓存方法cacheResult,后面两次没有调用缓存方法,直接返回了相同的值,说明已经缓存。然后修改入参为2,再调用2此,可以看到第一次调用了缓存方法,第二次直接返回缓存的值。
2019-08-30 16:22:22.391 INFO 10028 --- [nio-8010-exec-3] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 16:22:22.392 INFO 10028 --- [nio-8010-exec-3] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo1
2019-08-30 16:22:22.394 INFO 10028 --- [nio-8010-exec-3] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 16:22:22 CST 2019
2019-08-30 16:22:22.395 INFO 10028 --- [nio-8010-exec-3] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:1
2019-08-30 16:22:22.431 INFO 10028 --- [nio-8010-exec-3] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:1
2019-08-30 16:22:22.434 INFO 10028 --- [nio-8010-exec-3] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:468
2019-08-30 16:22:22.435 INFO 10028 --- [nio-8010-exec-3] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 16:22:28.231 INFO 10028 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 16:22:28.276 INFO 10028 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo1
2019-08-30 16:22:28.278 INFO 10028 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 16:22:28 CST 2019
2019-08-30 16:22:28.279 INFO 10028 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:1
2019-08-30 16:22:28.281 INFO 10028 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:468
2019-08-30 16:22:28.316 INFO 10028 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 16:22:30.160 INFO 10028 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 16:22:30.238 INFO 10028 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo1
2019-08-30 16:22:30.238 INFO 10028 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 16:22:30 CST 2019
2019-08-30 16:22:30.243 INFO 10028 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:1
2019-08-30 16:22:30.244 INFO 10028 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:468
2019-08-30 16:22:30.244 INFO 10028 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 16:22:35.182 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 16:22:35.288 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo1
2019-08-30 16:22:35.289 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 16:22:35 CST 2019
2019-08-30 16:22:35.289 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:2
2019-08-30 16:22:35.290 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:2
2019-08-30 16:22:35.297 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:514
2019-08-30 16:22:35.297 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 16:22:37.726 INFO 10028 --- [nio-8010-exec-8] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 16:22:37.751 INFO 10028 --- [nio-8010-exec-8] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo1
2019-08-30 16:22:37.758 INFO 10028 --- [nio-8010-exec-8] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 16:22:37 CST 2019
2019-08-30 16:22:37.758 INFO 10028 --- [nio-8010-exec-8] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:2
2019-08-30 16:22:37.765 INFO 10028 --- [nio-8010-exec-8] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:514
2019-08-30 16:22:37.765 INFO 10028 --- [nio-8010-exec-8] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2、在同一个方法中调用缓存方法没有缓存效果?
在网上会看到一些帖子中的注意事项是不要在同一个类中调用缓存方法,否则没有效果。其实这种说法没有讲到根本原因,在上面的例子中我们也是调用本身类中的方法,但是仍然有效果。不过细心点可以看到我们这个类在实例域中注入了本身类的bean,然后demo1在调用缓存方法时是用这个实例域去调用的。
@Resource
private ICacheDemoSV cacheDemoSV;
我们再写一个demo2,直接调用缓存方法看一下效果,代码如下:
/**
* 演示不使用代理的情况,无法正常缓存数据
* @param key 输入的key
*/
@Override
public void demo2(int key) {
LOGGER.info("***************************");
LOGGER.info("方法:demo2");
LOGGER.info("时间为:" + new Date().toString());
LOGGER.info("输入值为:" + key);
int result = cacheResult(key);
LOGGER.info("缓存方法的返回值为:" + result);
LOGGER.info("***************************");
}
调用3次,日志如下:
2019-08-30 17:18:56.348 INFO 10028 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 17:18:56.375 INFO 10028 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo2
2019-08-30 17:18:56.381 INFO 10028 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 17:18:56 CST 2019
2019-08-30 17:18:56.382 INFO 10028 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:1
2019-08-30 17:18:56.388 INFO 10028 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:1
2019-08-30 17:18:56.388 INFO 10028 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:468
2019-08-30 17:18:56.388 INFO 10028 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 17:18:57.249 INFO 10028 --- [io-8010-exec-10] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 17:18:57.256 INFO 10028 --- [io-8010-exec-10] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo2
2019-08-30 17:18:57.256 INFO 10028 --- [io-8010-exec-10] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 17:18:57 CST 2019
2019-08-30 17:18:57.257 INFO 10028 --- [io-8010-exec-10] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:1
2019-08-30 17:18:57.257 INFO 10028 --- [io-8010-exec-10] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:1
2019-08-30 17:18:57.258 INFO 10028 --- [io-8010-exec-10] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:840
2019-08-30 17:18:57.258 INFO 10028 --- [io-8010-exec-10] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 17:18:58.051 INFO 10028 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 17:18:58.058 INFO 10028 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo2
2019-08-30 17:18:58.066 INFO 10028 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 17:18:58 CST 2019
2019-08-30 17:18:58.067 INFO 10028 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:1
2019-08-30 17:18:58.067 INFO 10028 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:1
2019-08-30 17:18:58.068 INFO 10028 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:58
2019-08-30 17:18:58.073 INFO 10028 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
调用了三次,每一次都调用了缓存方法cacheResult,每一次的结果也不相同
原因很简单,因为SpringCache也是基于AOP实现的,当我们通过Spring容器中的bean去调用方法的时候,是调用Spring为我们动态生成的代理类的方法,代理类中再真正调用此方法逻辑前会先调用缓存拦截器去获取缓存,如果成功获取缓存则直接返回,否则调用方法逻辑,并将结果缓存。但是如果在一个方法中直接调用另一个方法,实际上是用this去调用这个方法,这时调用的不是Spring生成的动态代理类的方法,所以根本不存在读缓存写缓存这些操作了。
3、清除缓存@CacheEvict的演示
代码如下,分别先调两次缓存方法演示缓存效果,然后调用清除缓存的方法,然后再调查缓存的方法看一下效果。
/**
* 演示缓存与清除缓存
* @param key 输入的key
*/
@Override
public void demo3(int key) {
LOGGER.info("***************************");
LOGGER.info("方法:demo3");
LOGGER.info("时间为:" + new Date().toString());
LOGGER.info("输入值为:" + key);
int result = cacheDemoSV.cacheResult(key);
LOGGER.info("缓存方法的返回值为:" + result);
LOGGER.info("再调一次缓存方法");
result = cacheDemoSV.cacheResult(key);
LOGGER.info("缓存方法的返回值为:" + result);
LOGGER.info("清除缓存");
cacheDemoSV.removeCache(key);
LOGGER.info("再调一次缓存方法");
result = cacheDemoSV.cacheResult(key);
LOGGER.info("缓存方法的返回值为:" + result);
LOGGER.info("***************************");
}
日志如下,符合预期。第一次调用后缓存了值,第二次调用直接返回缓存值。然后清除缓存,清除后,再次调用又重新缓存了新的值。
2019-08-30 17:30:17.541 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 17:30:17.550 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo3
2019-08-30 17:30:17.557 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 17:30:17 CST 2019
2019-08-30 17:30:17.557 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:10
2019-08-30 17:30:17.558 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:10
2019-08-30 17:30:17.558 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:655
2019-08-30 17:30:17.564 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 再调一次缓存方法
2019-08-30 17:30:17.564 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:655
2019-08-30 17:30:17.564 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 清除缓存
2019-08-30 17:30:17.565 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 清除缓存,缓存的key为:10
2019-08-30 17:30:17.565 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 再调一次缓存方法
2019-08-30 17:30:17.566 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:10
2019-08-30 17:30:17.567 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:729
2019-08-30 17:30:17.567 INFO 10028 --- [nio-8010-exec-6] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
4、更新缓存@CachePut的演示
代码如下,分别先调两次缓存方法演示缓存效果,然后调用更新缓存的方法,然后再调查缓存的方法看一下效果。
/**
* 演示缓存与更新缓存
* @param key 输入的key
*/
@Override
public void demo4(int key) {
LOGGER.info("***************************");
LOGGER.info("方法:demo4");
LOGGER.info("时间为:" + new Date().toString());
LOGGER.info("输入值为:" + key);
int result = cacheDemoSV.cacheResult(key);
LOGGER.info("缓存方法的返回值为:" + result);
LOGGER.info("再调一次缓存方法");
result = cacheDemoSV.cacheResult(key);
LOGGER.info("缓存方法的返回值为:" + result);
LOGGER.info("调用更新缓存方法");
cacheDemoSV.updateCache(key);
LOGGER.info("再调一次缓存方法");
result = cacheDemoSV.cacheResult(key);
LOGGER.info("缓存方法的返回值为:" + result);
LOGGER.info("***************************");
}
日志如下,符合预期。第一次调用后缓存了值,第二次调用直接返回缓存值。然后调用更新缓存方法,更新缓存方法不管这个key是否已经缓存仍然会调用方法逻辑,并将新的返回值缓存,再次调用查缓存的方法则会直接返回上一次更新方法缓存的值。
2019-08-30 17:39:50.920 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 17:39:50.920 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo4
2019-08-30 17:39:50.921 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 17:39:50 CST 2019
2019-08-30 17:39:50.921 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:20
2019-08-30 17:39:50.979 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:20
2019-08-30 17:39:50.980 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:574
2019-08-30 17:39:50.980 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 再调一次缓存方法
2019-08-30 17:39:50.982 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:574
2019-08-30 17:39:50.992 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 调用更新缓存方法
2019-08-30 17:39:50.993 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 调用更新缓存方法:updateCache,缓存的key为:20
2019-08-30 17:39:50.994 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 更新后缓存的值为:47
2019-08-30 17:39:50.994 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 再调一次缓存方法
2019-08-30 17:39:50.996 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:47
2019-08-30 17:39:50.996 INFO 11068 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
三、结合Redis后遇到异常的时候如何不使用缓存继续读表
当我们使用Redis作为缓存工具时,如果redis集群故障了会抛异常,导致缓存方法不能正常使用。日志如下:
2019-08-30 18:09:46.483 INFO 9816 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-08-30 18:09:46.488 INFO 9816 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo1
2019-08-30 18:09:46.488 INFO 9816 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Fri Aug 30 18:09:46 CST 2019
2019-08-30 18:09:46.493 INFO 9816 --- [nio-8010-exec-1] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:1
2019-08-30 18:09:46.639 INFO 9816 --- [nio-8010-exec-1] io.lettuce.core.EpollProvider : Starting without optional epoll library
2019-08-30 18:09:46.647 INFO 9816 --- [nio-8010-exec-1] io.lettuce.core.KqueueProvider : Starting without optional kqueue library
2019-08-30 18:09:48.398 ERROR 9816 --- [nio-8010-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379] with root cause
java.net.ConnectException: Connection refused: no further information
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:327)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:340)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:616)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:563)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:480)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:442)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:745)
相关源码在AbstractCacheInvoker类中,可以看到这段代码中从缓存中获取数据,如果从缓存中获取数据异常,会调用异常处理器来进行处理。
/**
* Execute {@link Cache#get(Object)} on the specified {@link Cache} and
* invoke the error handler if an exception occurs. Return {@code null}
* if the handler does not throw any exception, which simulates a cache
* miss in case of error.
* @see Cache#get(Object)
*/
@Nullable
protected Cache.ValueWrapper doGet(Cache cache, Object key) {
try {
return cache.get(key);
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
return null; // If the exception is handled, return a cache miss
}
}
/**
* Return the {@link CacheErrorHandler} to use.
*/
public CacheErrorHandler getErrorHandler() {
return this.errorHandler;
}
默认的异常处理器(ErrorHandler)是SimpleCacheErrorHandler,代码如下,可以看到是不做任何处理,直接将异常抛出。
/**
* A simple {@link CacheErrorHandler} that does not handle the
* exception at all, simply throwing it back at the client.
*
* @author Stephane Nicoll
* @since 4.1
*/
public class SimpleCacheErrorHandler implements CacheErrorHandler {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
throw exception;
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, @Nullable Object value) {
throw exception;
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
throw exception;
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
throw exception;
}
}
如果我们写一个自定义的异常处理器替换原异常处理器,遇到异常后将异常处理掉,则在无法读取缓存的时候将会继续执行方法从数据库里获取最新的值。代码如下,我们添加一个配置类MyCachingConfigurer实现CachingConfigurer接口,并重写errorHandler方法,返回一个实现CacheErrorHandler接口的匿名内部类的实例。
这个匿名内部类中重写了四种缓存处理方法,均经异常处理掉,不抛出(为了演示清晰,异常栈没有输出到日志中)。
@Configuration
public class MyCachingConfigurer implements CachingConfigurer {
private static final Logger LOGGER = LoggerFactory.getLogger(MyCachingConfigurer.class);
@Override
public CacheManager cacheManager() {
return null;
}
@Override
public CacheResolver cacheResolver() {
return null;
}
@Override
public KeyGenerator keyGenerator() {
return null;
}
@Override
public CacheErrorHandler errorHandler() {
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
LOGGER.error("调用方法:handleCacheGetError,Redis连接异常,无法使用缓存");
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
LOGGER.error("调用方法:handleCachePutError,Redis连接异常,无法使用缓存");
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
LOGGER.error("调用方法:handleCacheEvictError,Redis连接异常,无法使用缓存");
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
LOGGER.error("调用方法:handleCacheClearError,Redis连接异常,无法使用缓存");
}
};
}
}
修改完成后,再试一下调用缓存方法,日志如下,可看出异常被处理后,每次调用均真实调用了方法。
2019-09-01 11:41:20.457 INFO 8712 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-09-01 11:41:20.458 INFO 8712 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo1
2019-09-01 11:41:20.458 INFO 8712 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Sun Sep 01 11:41:20 CST 2019
2019-09-01 11:41:20.459 INFO 8712 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:2
2019-09-01 11:41:20.566 INFO 8712 --- [nio-8010-exec-2] io.lettuce.core.EpollProvider : Starting without optional epoll library
2019-09-01 11:41:20.568 INFO 8712 --- [nio-8010-exec-2] io.lettuce.core.KqueueProvider : Starting without optional kqueue library
2019-09-01 11:41:22.123 ERROR 8712 --- [nio-8010-exec-2] c.b.ms.base.common.MyCachingConfigurer : 调用方法:handleCacheGetError,Redis连接异常,无法使用缓存
2019-09-01 11:41:22.132 INFO 8712 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:2
2019-09-01 11:41:23.159 ERROR 8712 --- [nio-8010-exec-2] c.b.ms.base.common.MyCachingConfigurer : 调用方法:handleCachePutError,Redis连接异常,无法使用缓存
2019-09-01 11:41:23.162 INFO 8712 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:973
2019-09-01 11:41:23.163 INFO 8712 --- [nio-8010-exec-2] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-09-01 11:41:29.516 INFO 8712 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-09-01 11:41:29.518 INFO 8712 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo1
2019-09-01 11:41:29.519 INFO 8712 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Sun Sep 01 11:41:29 CST 2019
2019-09-01 11:41:29.520 INFO 8712 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:2
2019-09-01 11:41:30.537 ERROR 8712 --- [nio-8010-exec-5] c.b.ms.base.common.MyCachingConfigurer : 调用方法:handleCacheGetError,Redis连接异常,无法使用缓存
2019-09-01 11:41:30.541 INFO 8712 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:2
2019-09-01 11:41:31.573 ERROR 8712 --- [nio-8010-exec-5] c.b.ms.base.common.MyCachingConfigurer : 调用方法:handleCachePutError,Redis连接异常,无法使用缓存
2019-09-01 11:41:31.578 INFO 8712 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:420
2019-09-01 11:41:31.583 INFO 8712 --- [nio-8010-exec-5] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-09-01 11:41:32.523 INFO 8712 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
2019-09-01 11:41:32.526 INFO 8712 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : 方法:demo1
2019-09-01 11:41:32.526 INFO 8712 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : 时间为:Sun Sep 01 11:41:32 CST 2019
2019-09-01 11:41:32.528 INFO 8712 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : 输入值为:2
2019-09-01 11:41:33.538 ERROR 8712 --- [nio-8010-exec-4] c.b.ms.base.common.MyCachingConfigurer : 调用方法:handleCacheGetError,Redis连接异常,无法使用缓存
2019-09-01 11:41:33.543 INFO 8712 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : 调用缓存方法:cacheResult,缓存的key为:2
2019-09-01 11:41:34.554 ERROR 8712 --- [nio-8010-exec-4] c.b.ms.base.common.MyCachingConfigurer : 调用方法:handleCachePutError,Redis连接异常,无法使用缓存
2019-09-01 11:41:34.557 INFO 8712 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : 缓存方法的返回值为:382
2019-09-01 11:41:34.560 INFO 8712 --- [nio-8010-exec-4] c.b.m.base.service.impl.CacheDemoSVImpl : ***************************
四、如何清除缓存
有时候当我们启动实例时,需要将缓存清除,或者更改了数据库配置信息后需要清除缓存使得数据重新缓存。
1.可以利用RedisConnectionFactory。
代码如下。如果启动的时候需要清理redis中所有内容,则调用flushAll方法,如果只清除我们用到的缓存,那么定义一个key的前缀,所所有缓存的key都以这前缀开始,然后通过keys方法获取所有key的列表,然后调用del方法删除这些key对应得缓存。
@Component
public class RedisCacheInit {
@Resource
private RedisConnectionFactory redisConnectionFactory;
@Value("${spring.cache.type}")
private String type;
@PostConstruct
public void cleanCache() {
if ("redis".equals(type)) {
RedisConnection connection = redisConnectionFactory.getConnection();
//connection.flushAll();
Set keys = connection.keys((CacheNameConstants.CACHE_NAME_PREFIX + "*").getBytes());
if (keys.size() > 0) {
connection.del(keys.toArray(new byte[0][]));
}
}
}
}
}
2.利用CacheManager
代码如下。第一种方式仅限于使用Redis来缓存数据时,这种方式对于任意缓存实现都支持。可以通过CacheManager提供的getCacheNames拿到所有缓存的key,然后逐一清除。
public void cleanAllCache() {
Collection cacheNames = cacheManager.getCacheNames();
for (String cacheName : cacheNames) {
Cache cache = cacheManager.getCache(cacheName);
if (cache == null) {
continue;
}
cache.clear();
}
LOGGER.info("成功清除全部缓存");
}