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;
	}

	@Bean
	public 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;
	}

	@Bean
	public 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() {

            @Override
            public 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使用默认缓存管理器的缓存数据的基本操作方法和基本工作原理,欢迎文末留言。

相关推荐
<p> <strong><span style="font-size:20px;color:#FF0000;">本课程主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者</span></strong> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">1. 包含:<span style="color:#FFFF00;background-color:#FF0000;">项目源码、</span><span style="color:#FFFF00;background-color:#FF0000;">项目文档、数据库脚本、软件工具</span>等所有资料</span></strong></span> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">2. 手把手的带你从零开始部署运行本套系统</span></strong></span> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">3. 该项目附带的源码资料可作为毕设使用</span></strong></span> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">4. 提供技术答疑和远程协助指导</span></strong></span><strong><span style="font-size:18px;"></span></strong> </p> <p> <br /> </p> <p> <span style="font-size:18px;"><strong>项目运行截图:</strong></span> </p> <p> <strong><span style="font-size:18px;">1)系统登陆界面</span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241015433522.png" alt="" /><br /> </span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">2)学生模块</span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241015575966.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">3)教师模块</span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241016127898.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">4)系统管理员</span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241016281177.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241016369884.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><br /> </span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">更多Java毕设项目请关注我的毕设系列课程 <a href="https://edu.csdn.net/lecturer/2104">https://edu.csdn.net/lecturer/2104</a></span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><br /> </span></strong> </p>
<p> 课程演示环境:Windows10  </p> <p> 需要学习<span>Ubuntus</span>系统<span>YOLOv4-tiny</span>的同学请前往《<span>YOLOv4-tiny</span>目标检测实战:训练自己的数据集》 <span></span> </p> <p> <span> </span> </p> <p> <span style="color:#E53333;">YOLOv4-tiny</span><span style="color:#E53333;">来了!速度大幅提升!</span><span></span> </p> <p> <span> </span> </p> <p> <span>YOLOv4-tiny</span>在<span>COCO</span>上的性能可达到:<span>40.2% AP50, 371 FPS (GTX 1080 Ti)</span>。相较于<span>YOLOv3-tiny</span>,<span>AP</span>和<span>FPS</span>的性能有巨大提升。并且,<span>YOLOv4-tiny</span>的权重文件只有<span>23MB</span>,适合在移动端、嵌入式设备、边缘计算设备上部署。<span></span> </p> <p> <span> </span> </p> <p> 本课程将手把手地教大家使用<span>labelImg</span>标注和使用<span>YOLOv4-tiny</span>训练自己的数据集。课程实战分为两个项目:单目标检测(足球目标检测)和多目标检测(足球和梅西同时检测)。<span></span> </p> <p> <span> </span> </p> <p> 本课程的<span>YOLOv4-tiny</span>使用<span>AlexAB/darknet</span>,在<span>Windows10</span>系统上做项目演示。包括:<span>YOLOv4-tiny</span>的网络结构、安装<span>YOLOv4-tiny</span>、标注自己的数据集、整理自己的数据集、修改配置文件、训练自己的数据集、测试训练出的网络模型、性能统计<span>(mAP</span>计算<span>)</span>和先验框聚类分析。 <span> </span> </p> <p> <span> </span> </p> <p> 除本课程《<span>Windows</span>版<span>YOLOv4-tiny</span>目标检测实战:训练自己的数据集》外,本人推出了有关<span>YOLOv4</span>目标检测的系列课程。请持续关注该系列的其它视频课程,包括:<span></span> </p> <p> 《<span>Windows</span>版<span>YOLOv4</span>目标检测实战:训练自己的数据集》<span></span> </p> <p> 《<span>Windows</span>版<span>YOLOv4</span>目标检测实战:人脸口罩佩戴识别》<span></span> </p> <p> 《<span>Windows</span>版<span>YOLOv4</span>目标检测实战:中国交通标志识别》<span></span> </p> <p> 《<span>Windows</span>版<span>YOLOv4</span>目标检测:原理与源码解析》<span></span> </p> <p> <span> <img alt="" src="https://img-bss.csdnimg.cn/202007061503586145.jpg" /></span> </p> <p> <span><img alt="" src="https://img-bss.csdnimg.cn/202007061504169339.jpg" /><br /> </span> </p>
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页