玩转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做不了不代表我们自己做不了,因此有兴趣的同学可以在此基础上,扩展出可以自定义超时时间的能力~~~

相关文章
|
9天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
52 14
|
20小时前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
|
20小时前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
20小时前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
27天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
102 2
|
3月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
243 2
|
2天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
1月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
49 1
SpringBoot入门(7)- 配置热部署devtools工具
|
1月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
47 2
 SpringBoot入门(7)- 配置热部署devtools工具