玩转Spring Cache --- @Cacheable/@CachePut/@CacheEvict注解的原理深度剖析和使用【享学Spring】(下)

简介: 玩转Spring Cache --- @Cacheable/@CachePut/@CacheEvict注解的原理深度剖析和使用【享学Spring】(下)

缓存注解使用案例


关于缓存注解的常规使用案例,我觉得本文没有必要介绍。

接下来主要讲解一些特殊的使用:

若方法返回值为null,还会缓存吗?

比如上例返回值改为null:


@Service
public class CacheDemoServiceImpl implements CacheDemoService {
    @Cacheable(cacheNames = "demoCache", key = "#id")
    @Override
    public Object getFromDB(Integer id) {
        System.out.println("模拟去db查询~~~" + id);
        //return "hello cache...";
        return null;
    }
}


执行单测:


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {
    @Autowired
    private CacheDemoService cacheDemoService;
    @Autowired
    private CacheManager cacheManager;
    @Test
    public void test1() {
        cacheDemoService.getFromDB(1);
        cacheDemoService.getFromDB(1);
        System.out.println("----------验证缓存是否生效----------");
        Cache cache = cacheManager.getCache("demoCache");
        System.out.println(cache);
        System.out.println(cache.get(1, String.class));
    }
}


结果打印:


模拟去db查询~~~1
----------验证缓存是否生效----------
org.springframework.cache.concurrent.ConcurrentMapCache@15f2eda3
null


结论是:默认情况下,返回值是null也是会缓存的(第二次过来就不会再查询了)。通过一个断点会更清晰:


image.png


但假如修改缓存注解如下:

// 注意:unless 是非的意思
@Cacheable(cacheNames = "demoCache", key = "#id",unless = "#result == null")


运行打印如下:


模拟去db查询~~~1
模拟去db查询~~~1
----------验证缓存是否生效----------
org.springframework.cache.concurrent.ConcurrentMapCache@6a282fdd
null


查了两次DB,证明此时返回null就不会再缓存了,unless生效。


倘若修改配置如下:


    @Bean
    public CacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        cacheManager.setAllowNullValues(false);
        return cacheManager;
    }


运行则报错:


java.lang.IllegalArgumentException: Cache 'demoCache' is configured to not allow null values but null was provided


一般情况下,不建议这么设置,因为一般都是允许缓存null值的。


@Cacheable注解sync=true的效果

在多线程环境下,某些操作可能使用相同参数同步调用(相同的key)。默认情况下,缓存不锁定任何资源,可能导致多次计算,而违反了缓存的目的。对于这些特定的情况,属性 sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中(Spring4.3提供的)。


下面给个例子直接看看效果就成:


@Cacheable(cacheNames = "demoCache", key = "#id")


测试Demo(多线程):



@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {
    @Autowired
    private CacheDemoService cacheDemoService;
    @Test
    public void test1() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                cacheDemoService.getFromDB(1);
            }).start();
        }
        // 保证main线程不要这么快结束(否则没有日志结果的~~~)
        TimeUnit.SECONDS.sleep(10);
    }
}


打印结果:


模拟去db查询~~~1
模拟去db查询~~~1
模拟去db查询~~~1
模拟去db查询~~~1
模拟去db查询~~~1


打印可能5次、可能6次不确定。但是若我们sync=true了呢?


@Cacheable(cacheNames = "demoCache", key = "#id", sync = true)


再次运行测试打印结果如下:


模拟去db查询~~~1


永远只会打印一次。 所以在高并发环境下,我个人是十分建议开启此同步开关的,至于非高并发,无所谓啦~

注解可重复标注吗?


因为源码都分析了,所以此处看结论即可。

    @CachePut(cacheNames = "demoCache", key = "#id") // 不同的注解可以标注多个
    //@Cacheable(cacheNames = "demoCache", key = "#id") // 相同注解标注两个是不行的 因为它并不是@Repeatable的
    @Cacheable(cacheNames = "demoCache", key = "#id")
    @Override
    public Object getFromDB(Integer id) {
        System.out.println("模拟去db查询~~~" + id);
        return "hello cache...";
    }


不同的注解可以标注多个,且都能生效。若需要相同注解标注多个等更复杂的场景,推荐使用@Caching注解


如何给缓存注解设置专用的key生成器、缓存管理器等等


标准写法是这样的:

@EnableCaching
@Configuration
public class CacheConfig implements CachingConfigurer {
    @Override
    public CacheResolver cacheResolver() {
        return null;
    }
    @Override
    public KeyGenerator keyGenerator() {
        return null;
    }
    @Override
    public CacheErrorHandler errorHandler() {
        return null;
    }
}



实现对应的方法,可以new一个对象返回,也可以用容器内的。


大多数情况下我们都不需要特别的指定缓存注解使用的管理器,因为它自己会去容器里找。 但是,但是,但是当你使用了多套缓存时,我还是建议显示的指定的。


总结


本篇文章相对较长,因为从原理处深度分析了Spring Cache的执行过程,希望能帮助到大家做到心里有数,从而更加健康的书写代码和扩展功能。


关于缓存注解这块,我相信很多人都有一个痛点:注解并不支持设置过期时间。其实我想说,如果你看了上篇文章就知道,这个Spring它做不了,因为它并没有对Expire进行抽象。

但是Spring做不了不代表我们自己做不了,因此有兴趣的同学可以在此基础上,扩展出可以自定义超时时间的能力~~~

相关文章
|
3天前
|
Java
Springboot 使用自定义注解结合AOP方式校验接口参数
Springboot 使用自定义注解结合AOP方式校验接口参数
Springboot 使用自定义注解结合AOP方式校验接口参数
|
4天前
|
存储 缓存 Java
【JavaEE】Spring中注解的方式去获取Bean对象
【JavaEE】Spring中注解的方式去获取Bean对象
3 0
|
4天前
|
存储 Java 对象存储
【JavaEE】Spring中注解的方式去存储Bean对象
【JavaEE】Spring中注解的方式去存储Bean对象
7 0
|
4天前
|
监控 安全 Java
Spring cloud原理详解
Spring cloud原理详解
18 0
|
4天前
|
缓存 NoSQL Java
Spring Cache 缓存原理与 Redis 实践
Spring Cache 缓存原理与 Redis 实践
215 0
|
4天前
|
缓存 NoSQL Java
spring cache整合redis实现springboot项目中的缓存功能
spring cache整合redis实现springboot项目中的缓存功能
51 1
|
4天前
|
缓存 NoSQL Java
Spring Cache 整合 Redis 做缓存使用~ 快速上手~
Spring Cache 整合 Redis 做缓存使用~ 快速上手~
73 1
|
5月前
|
缓存 NoSQL Java
spring cache使用redis
spring cache使用redis
50 1
|
存储 缓存 NoSQL
玩转Spring Cache --- 整合分布式缓存Redis Cache(使用Lettuce、使用Spring Data Redis)【享学Spring】(上)
玩转Spring Cache --- 整合分布式缓存Redis Cache(使用Lettuce、使用Spring Data Redis)【享学Spring】(上)
玩转Spring Cache --- 整合分布式缓存Redis Cache(使用Lettuce、使用Spring Data Redis)【享学Spring】(上)
|
存储 JSON 移动开发
Spring boot 整合 cache,session,redis
Spring boot 整合 cache,session,redis
578 0