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

相关文章
|
5月前
|
缓存 监控 Java
SpringBoot @Scheduled 注解详解
使用`@Scheduled`注解实现方法周期性执行,支持固定间隔、延迟或Cron表达式触发,基于Spring Task,适用于日志清理、数据同步等定时任务场景。需启用`@EnableScheduling`,注意线程阻塞与分布式重复问题,推荐结合`@Async`异步处理,提升任务调度效率。
877 128
|
5月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
1970 0
|
4月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
4月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
568 2
|
5月前
|
XML Java 数据格式
常用SpringBoot注解汇总与用法说明
这些注解的使用和组合是Spring Boot快速开发和微服务实现的基础,通过它们,可以有效地指导Spring容器进行类发现、自动装配、配置、代理和管理等核心功能。开发者应当根据项目实际需求,运用这些注解来优化代码结构和服务逻辑。
425 12
|
5月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
308 0
探索Spring Boot的@Conditional注解的上下文配置
|
7月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1188 0
|
8月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
960 0
|
4月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
476 3
|
4月前
|
Java 测试技术 数据库连接
【SpringBoot(四)】还不懂文件上传?JUnit使用?本文带你了解SpringBoot的文件上传、异常处理、组件注入等知识!并且带你领悟JUnit单元测试的使用!
Spring专栏第四章,本文带你上手 SpringBoot 的文件上传、异常处理、组件注入等功能 并且为你演示Junit5的基础上手体验
1004 2