SpringBoot中使用SpringCache以及结合Redis的使用

一、如何使用

    

网上各种教程比较多,简单介绍一下,不写那么详细。

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("成功清除全部缓存");
    }

 

你可能感兴趣的:(学习)