Spring Boot————默认缓存应用及原理

引言

应用程序的数据除了可以放在配置文件中、数据库中以外,还会有相当一部分存储在计算机的内存中,这部分数据访问速度要快于数据库的访问,因此通常在做提升数据访问速度时,会将需要提升访问速度的数据放入到内存中,我们称之为缓存

最常用的缓存方式是使用并发容器,因为具有比较高的并发性能,因此Spring的默认缓存策略就是使用ConcurrentHashMap作为缓存容器。下面将会逐步展开缓存的概念与Spring中的使用规则。

一、JSR-107缓存API

为了统一缓存的开发规范,以及提升系统的扩展性,J2EE发布了JSR-107缓存规范。主要定义了五大核心接口:

CachingProvider、CacheManager、Cache、Entry、Expiry

而实际开发中,我们通常会使用Spring缓存抽象来完成对缓存的操作,它是Spring为开发者定义的一套用于管理缓存的接口及相关实现。而Spring缓存抽象底层的概念与这五大接口的描述都是通用的,因此了解JSR-107定义的相关概念以及API接口描述,将有助于我们学习Spring的缓存抽象。

1.1 接口定义

1、CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。

2、CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。

3、Cache:这是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。

4、Entry:它是一个存储在Cache中的Key-Value对。

5、Expiry:每一个存储在Cache中的条目都有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新、删除。缓存有效期可以通过ExpiryPolicy设置。

1.2 接口关系图谱

二、Spring缓存抽象(以下重点)

2.1 Spring缓存接口

Spring从3.1开始定义了:org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术,并支持使用JCache(JSR-107)注解简化我们的开发。那么按照JSR-107的缓存思想,CacheManager就是用于管理Cache的,而Cache则是真正对缓存进行操作的抽象。

1、Cache接口是具体的缓存组件的规范定义,包含对缓存数据的各种操作;

2、Cache接口下Spring提供了各种xxxCache组件(实现类),如RedisCache,EhCacheCache,ConcurrentMapCache等。

每次调用需要缓存功能的方法时,Spring都会检查指定参数的指定目标方法是否已经被调用过。如果有就直接从缓存中获取方法调用后的结果;如果没有就调用方法并缓存结果后返回给用户,下次调用直接从缓存中获取。

使用Spring缓存抽象时我们需要注意以下两点:

1、确定哪些方法需要被缓存以及它们的缓存策略。

2、从缓存中读取之前缓存存储的数据。

2.2 Spring 缓存注解

最常用的缓存注解有如下:

@Cacheable主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。

@CacheEvict清空缓存。用于标注在一些删除方法上。

@CachePut保证方法被调用,又希望结果被缓存。用于标注在一些更新方法上,更新缓存。

@EnableCaching开启基于注解的缓存。

2.3 缓存策略

keyGengerator缓存数据时key的生成策略。

serialize缓存数据时value的序列化策略。

三、Spring缓存快速入门

自进入Spring Boot时代,很多功能都已经不再需要繁杂的Java代码来实现,而是使用注解来完成相同的功能,下面将介绍如何使用注解的方式来完成一整套关于Spring默认缓存数据的操作。

3.1 开启基于注解的缓存功能

首先,如果希望使用注解的方式使用缓存,那么就需要开启基于注解的缓存功能。

具体方法是在Spring Boot的主程序上加上@EnableCaching注解:

@EnableJpaRepositories
@SpringBootApplication
@EnableCaching
public class CourseSystemApplication {public static void main(String[] args) {SpringApplication.run(CourseSystemApplication.class, args);}
}

3.2 自定义缓存组件

CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字。因此,在使用注解定义缓存组件的时候需要指定一些属性:

cacheNames / value:指定缓存组件的名称,两个属性都是指定缓存名称,二选一。

key:缓存数据使用的key。默认是使用方法参数的值。而缓存的数据就是方法返回值。另外key也可以使用SpEL表达式来表示。

keyGenerator:key的生成器,与key属性二选一。

cacheManager:指定缓存管理器,或者cacheResolver指定缓存解析器。

condition:指定符合条件的情况下才缓存。

unless:否定缓存。当unless指定的条件为true,方法返回值就不会缓存。也可以获取到结果判断是否需要缓存,如:        unless = “#result == null”,就代表如果结果是null就不缓存。

sync:是否使用异步模式进行缓存。注意!!sync属性默认为false,如果为true,则unless属性将不再支持

附 SpEL表达式指定key的规则 

3.3 代码示例

在查询全部课程的serviceImpl方法上使用缓存注解@Cacheable。

@Cacheable(cacheNames = "courses")  
public List<Course> findAllCourses() {  List<Course> allCourses = couseRep.findAll();  logger.info("查询全部课程 : " + allCourses);  return allCourses;
}

查询效果 : 

第一次查询,有SQL日志打印;第二次以后都没有SQL打印,说明缓存生效了

问题描述 

1、第一次在Service接口中标记了缓存注解,没有生效。原因很可能是因为当service组件自动注入的时候实则是实现类在真正执行操作,因此,只有在真正使用的组件上使用缓存才能够起作用。因此,以interface--Impl的形式开发的时候,要将缓存注解标记在具体实现类上,否则会失效。

2、给key属性赋值一个普通的字符串,报:SpelEvaluationException异常。因此这个key只能使用SpEl表达式来描述。像上面这种没有参数的情况,可以不必指定key属性,spring会默认为缓存数据生成一个key。

四、@Cacheable缓存工作原理

4.1 缓存组件的自动配置

缓存的相关配置来自于CacheAutoConfiguration类。

这个类使用@Import注解向容器中导入一个CacheConfigurationImportSelector的静态内部类,和其他自动配置时的导入选择器类似,它也是ImportSelector的实现类,这些实现类只有一个方法:String[] selectImports(...),专门用来导入具体的JavaConfig配置类。

而CacheConfigurationImportSelector,负责导入各种缓存组件的配置类。通过在selectImports内部打上断点debug启动项目的方式,我们可以一览返回值String[]中的内容:

[  org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration,   org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration,   org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration,   org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration,   org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration,   org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration,   org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration,   org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration,   org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration,   org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration,   org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration  
]

这些缓存配置,就是Spring缓存抽象的具体底层实现所需要用到的JavaConfig。

这些配置类内部都会有一些规则判断,@ConditionalXxx来判断是否生效。以第一个GenericCacheConfiguration为例:

@Configuration
@ConditionalOnBean(Cache.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class GenericCacheConfiguration {private final CacheManagerCustomizers customizers;GenericCacheConfiguration(CacheManagerCustomizers customizers) {this.customizers = customizers;}@Beanpublic SimpleCacheManager cacheManager(Collection<Cache> caches) {SimpleCacheManager cacheManager = new SimpleCacheManager();cacheManager.setCaches(caches);return this.customizers.customize(cacheManager);}
}

可以看到类头上,相关的@Conditional注解来表明这个配置类在哪种情况下生效。但是我们也可以使用spring boot的自动配置报告来查看究竟是哪个缓存配置类生效。

4.2 SimpleCacheConfiguration

通过在全局配置文件中设置debug=true,使用spring boot的自动配置报告功能,打印匹配的JavaConfig配置类,我们可以看到默认启用的缓存配置是SimpleCacheConfiguration

打开这个配置类:

/*** Simplest cache configuration, usually used as a fallback.** @author Stephane Nicoll* @since 1.3.0*/
@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {private final CacheProperties cacheProperties;private final CacheManagerCustomizers customizerInvoker;SimpleCacheConfiguration(CacheProperties cacheProperties,CacheManagerCustomizers customizerInvoker) {this.cacheProperties = cacheProperties;this.customizerInvoker = customizerInvoker;}@Beanpublic ConcurrentMapCacheManager cacheManager() {ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();List<String> cacheNames = this.cacheProperties.getCacheNames();if (!cacheNames.isEmpty()) {cacheManager.setCacheNames(cacheNames);}return this.customizerInvoker.customize(cacheManager);}
}

这个配置类通过@Bean向容器中注册了一个ConcurrentMapCacheManager对象,这个CacheManager可以获取和创建ConcurrentMapCache类型的缓存组件。

4.3 缓存执行流程(重点)

使用@Cacheable时:

1、在方法service方法第一次执行前,先去查询Cache(缓存组件)。它是按照cacheNames指定的名字调用CacheManager中的getCache(String name)方法获取的。

@Override
public Cache getCache(String name) {Cache cache = this.cacheMap.get(name);if (cache == null && this.dynamic) {synchronized (this.cacheMap) {cache = this.cacheMap.get(name);if (cache == null) {cache = createConcurrentMapCache(name);this.cacheMap.put(name, cache);}}}return cache;
}

当第一次执行时未找到指定缓存,那么就会去调用createConcurrentMapCache(name)创建这个缓存,并将key-value放入到缓存中。

2、去Cache中查找缓存,使用一个key,这个key默认是使用SimpleKeyGenerator生成key。

SimpleKeyGengerator生成key的默认策略:

/*** Generate a key based on the specified parameters.*/
public static Object generateKey(Object... params) {if (params.length == 0) {return SimpleKey.EMPTY;}if (params.length == 1) {Object param = params[0];if (param != null && !param.getClass().isArray()) {return param;}}return new SimpleKey(params);
}

如果没有参数,那么key就是用一个SimpleKey对象;如果有一个参数,则key = 参数的值;

如果有多个参数,key = new SimpleKey(params);

3、没有查找到缓存就调用目标方法。

4、将目标方法返回的结果,放入缓存中。

总结 

由@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询,如果没有就运行方法,并将结果放入缓存,以后再调用就可以直接是用缓存中的数据。

核心

1、是用CacheManager(默认ConcurrentMapCacheManager)按照名字得到Cache(默认ConcurrentMapCache)组件。

2、key使用keyGenerator生成的,默认的是SimpleKeyGenerator

五、自定义keyGenerator

自定义的key有两种实现手段,一种是通过@Cacheable的key属性,另一个是通过keyGenerator属性。

key属性使用的是SpEL表达式来指定key的格式规则,如:key = “#root.methodName+’[’+#id+’]’”,但是一种比较零散的自定义策略。

而keyGenerator可以指定一个通用的缓存key的生成策略。在这里简单说一下代码实现。

@Configuration
public class SysCacheKeyGenerator {/*** 课程缓存key生成策略*/@Bean("courseDefaultKeyGenerator")public KeyGenerator courseCacheKeyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {String key = params.length == 0 ? "courseList" : params.toString();return method.getName() + "." + key;}};}
}

使用这段代码来定义一个自定义的keyGenerator,并注册到容器中,然后配置@Cacheable的keyGenerator属性:

@Override
@Cacheable(cacheNames = "courses", keyGenerator = "courseDefaultKeyGenerator")
public List<Course> findAllCourses() {List<Course> allCourses = couseRep.findAll();logger.info("查询全部课程 : " + allCourses);return allCourses;
}

那么实际缓存时就会使用我们自定义的key:

六、设置缓存条件

@Cacheable注解,

condition属性可以动态的判断数据是否满足缓存条件。

如:condition = “#id > 1”,意思是判断参数id的值大于1的情况下才对结果缓存。

那么它的用法主要依赖于SpEl表达式的逻辑判断。

unless属性的意思是“不缓存”,同样是通过SpEl表达式来判断true或false,如果unless的条件成立,那么将不会对数据进行缓存。

如:unless = “#id == 2” ,意思是如果id的值为2,那么就不缓存结果了。

七、@CachePut

@CachePut注解意为更新缓存。

一般标注在涉及到数据库更新操作的方法上,在更新数据库中记录的同时也会更新缓存中对应的数据。

运行实际

1、先调用目标方法

2、将目标方法的结果缓存起来。

注意 !!!

在使用@CachePut的时候要注意缓存数据的key要与查询该缓存数据时所用的key保持一致,否则,两个key如果不一致,就会出现查询方法依然是缓存更新之前的数据。

这里key可以使用#result来取得返回结果,及其内部属性如#result.id,但是@Cacheable不能使用#result来取得结果值,想想这是为什么?(提示,执行时机)

八、@CacheEvict

@CacheEvict :清除缓存。

key:指定要清除的缓存。

allEntries = true 代表清除这个缓存中的所有数据。

beforeInvocation = false 代表清除缓存的操作是在方法执行后,这也是默认值。如果出现异常,那么就不会清除缓存。该属性如果为true,那么就代表在方法执行前清除缓存,无论方法是否出现异常。

九、@Caching与@CacheConfig

@Caching是一个复杂缓存的注解,可以指定多种缓存规则;@CacheConfig放在类头上,用于抽取一些公共的缓存属性配置,如可以指定公共的缓存名称。

总结

关于Spring默认缓存组件的使用,基本就是@Cacheable、@CachePut、@CacheEvict这三个注解,另外注意在使用它们之前,记得在Spring Boot的程序主类上开启@EnableCaching。

另外,一定要注意@Cacheable和@CachePut的执行时机,以及它们真正操作的key值,因为这个缓存数据是存储在内存中的,因此不像数据库中的数据那样直观明了,操作缓存中的数据要求开发者有比较好的数据管理规则,否则,很容易出现缓存数据失效的问题。

综上,就是关于Spring Boot使用默认缓存管理器的缓存数据的基本操作方法和基本工作原理,欢迎文末留言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558852.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode算法入门- Multiply Strings -day18

LeetCode算法入门- Multiply Strings -day18 题目介绍 Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2, also represented as a string. Example 1: Input: num1 “2”, num2 “3” Output: “6” Exampl…

Linux——VMware虚拟机安装CentOS步骤

一、下载CentOS.iso镜像 最地道的下载方式就是通过官网&#xff0c;大多数的网上连接会直接抛出网易、华为的镜像连接&#xff0c;实际上这些连接都可以在官网找到&#xff1a; 官网地址&#xff08;可直接百度搜索CentOS&#xff09;&#xff1a;https://www.centos.org/ 1…

Spring Boot——Redis安装配置与应用整合

引言 Spring Boot默认以ConcurrentHashMap作为缓存容器&#xff0c;但默认的缓存容器在简单的场景使用还是可以的&#xff0c;而作为NoSQL的代表&#xff0c;Redis可以做内存数据库、消息中间件都是不错的&#xff0c;而且有RedisDesktopManager作为可视化管理工具&#xff0c…

利用Aria2高速下载网盘文件

利用Aria2高速下载网盘文件 方法步骤&#xff1a; 下载文件 解压arial2&#xff0c;运行aria2启动.VBS添加插件&#xff0c;解压BaiduExporter-master.zip在Google浏览器扩展程序中chrome://extensions加载已经解压的扩展程序 选择BaiduExporter进行添加即可&#xff0c;打开…

MySQL——JSON_REPLACE()函数修改JSON属性值

引言 由于对mysql的函数并不了解&#xff0c;之前遇到了一个场景&#xff1a; mysql表中有一个字段res_content 是一个由longtext类型&#xff08;可以理解为一个更长的varchar&#xff09;保存的巨大的JSON对象&#xff0c;但是&#xff0c;由于录入的疏忽&#xff0c;导致这…

Spring Boot整合Redis——自定义RedisSerializer

引言 spring boot简单引入redis依赖&#xff0c;并使用RedisTemplate进行对象存储时&#xff0c;需要使存储对象实现Serializable接口&#xff0c;这样才能够成功将对象进行序列化。 RedisTemplate默认使用的序列化机制是JdkSerializationRedisSerializer&#xff0c;但实际开…

交易系统如何确保账簿100%准确

转自廖雪峰老师的《交易系统如何确保账簿100%准确》 这篇文章阐述了一个交易系统中对账功能的关键&#xff0c;即&#xff1a;时刻保证资产负债表总额始终为 0。 交易系统中&#xff0c;对账是一个大问题。对账处理不好&#xff0c;不但需要花费大量的人力去处理账簿&#xff…

通俗易懂的SpringBoot教程---day1---Springboot入门教程介绍

通俗易懂的SpringBoot教程—day1—教程介绍 教程介绍&#xff1a; 初级教程&#xff1a; 一、 Spring Boot入门 二、 Spring Boot配置 三、 Spring Boot与日志 四、 Spring Boot与Web开发 五、 Spring Boot与Docker&#xff1a;Docker容器 六、 Spring Boot与数据访问&#x…

Java 8中获取参数名称

本文转自廖雪峰老师的&#xff1a;《在Java 8中获取参数名称》 在Java 8之前的版本&#xff0c;代码编译为class文件后&#xff0c;方法参数的类型是固定的&#xff0c;但参数名称却丢失了&#xff0c;这和动态语言严重依赖参数名称形成了鲜明对比。现在&#xff0c;Java 8开始…

通俗易懂的SpringBoot教程---day2---Springboot配置文件

通俗易懂的SpringBoot教程—day2—Springboot配置文件 1、配置文件 SpringBoot使用一个全局的配置文件&#xff0c;配置文件名是固定的&#xff1b; •application.properties •application.yml 配置文件的作用&#xff1a;修改SpringBoot自动配置的默认值&#xff1b;Spring…

Could not resolve host: 'localhost 报错解决办法

Could not resolve host: localhost 报错解决办法 面向Windows的&#xff1a; 零基础的我一直卡在这一步骤下&#xff1a; 首先要先在Windows安装curl&#xff1a;安装方式参考&#xff1a;https://blog.csdn.net/weixin_41986096/article/details/86646365 按照完之后&…

当面试官问我————为什么String是final的?

面试官&#xff1a;你好&#xff0c;能看得清下面这张图吗&#xff1f; 我&#xff1a;可以的。 面试官&#xff1a;恩&#xff0c;好的。呃&#xff0c;你能不能说一说为什么String要用final修饰&#xff1f; 我&#xff1a;final意味着不能被继承或者被重写&#xff0c;Str…

当面试官问我————Java是值传递还是引用传递?

面试官&#xff1a;你好&#xff0c;你能说出下面个程序的执行结果吗&#xff1f; public class Test {public static void main(String[] args) {String name "Scott";int age 5;User user new User();user.setName(name);user.setAge(age);System.out.println(…

ubuntu系统下Jenkins和tomcat的安装与配置

ubuntu 安装 JDK ubuntu的安装我们采取最简单的方式安装 直接用apt-get的方式 sudo apt-get install openjdk-8-jdk 安装器会提示你同意 oracle 的服务条款,选择 ok 然后选择yes 即可 ubuntu 安装tomcat8 通过apt安装 tomcat8 sudo apt-get install tomcat8 tomcat8-docs t…

String字符串拼接小例

>>>写出下面程序运行结果&#xff1a; public class StringTest {public static void main(String[] args) {String s1 "Programming";String a "Program";String b "ming";String s2 "Program" "ming";Stri…

看完这篇文章,还不懂nginx,算我输

看完这篇文章&#xff0c;还不懂nginx&#xff0c;算我输 参考&#xff1a;https://mp.weixin.qq.com/s/PeNWaCDf_6gp2fCQa0Gvng 1. Nginx产生~ Nginx 同 Apache 一样都是一种 Web 服务器。基于 REST 架构风格&#xff0c;以统一资源描述符&#xff08;Uniform Resources Id…

一篇博客读懂设计模式之---工厂模式

设计模式之—工厂模式 工厂模式&#xff1a; 创建过程&#xff1a; 创建Shape接口 public interface Shape {void draw(); }创建实现类&#xff1a; public class Circle implements Shape {Overridepublic void draw() {System.out.println("this is a circle!"…

一篇博客读懂设计模式之-----策略模式

设计模式之策略模式 在策略模式中&#xff0c;我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的对象 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。 主要解决&#xff1a;在有多种算法相似的情况下&#xff0c;使用 if…else 所带来的复杂和…

Java并发编程实战————恢复中断

中断是一种协作机制&#xff0c;一个线程不能强制其他线程停止正在执行的操作而去执行其他操作。 什么是中断状态&#xff1f; 线程类有一个描述自身是否被中断了的boolean类型的状态&#xff0c;可以通过调用 .isInterrupted() 方法来查看。官方解释如下&#xff1a; 简单来…

一篇博客读懂设计模式之---模板方法模式

设计模式之模板方法模式&#xff1a; 定义一个操作中的算法的骨架&#xff0c;而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 简而言之就是&#xff1a;父类定义了骨架&#xff08;调用哪些方法及其顺序&#xff09;…