SpringBoot基础系列-SpringCache使用
原创文章,转载请标注出处:《SpringBoot基础系列-SpringCache使用》
一、概述
SpringCache本身是一个缓存体系的抽象实现,并没有具体的缓存能力,要使用SpringCache还需要配合具体的缓存实现来完成。
虽然如此,但是SpringCache是所有Spring支持的缓存结构的基础,而且所有的缓存的使用最后都要归结于SpringCache,那么一来,要想使用SpringCache,还是要仔细研究一下的。
二、缓存注解
SpringCache缓存功能的实现是依靠下面的这几个注解完成的。
- @EnableCaching:开启缓存功能
- @Cacheable:定义缓存,用于触发缓存
- @CachePut:定义更新缓存,触发缓存更新
- @CacheEvict:定义清除缓存,触发缓存清除
- @Caching:组合定义多种缓存功能
- @CacheConfig:定义公共设置,位于class之上
2.1 @EnableCaching
该注解主要用于开启基于注解的缓存功能,使用方式为:
@EnableCaching
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
}
}
注意:在SpringBoot中使用SpringCache可以由自动配置功能来完成CacheManager的注册,SpringBoot会自动发现项目中拥有的缓存系统,而注册对应的缓存管理器,当然我们也可以手动指定。
使用该注解和如下XML配置具有一样的效果:
<beans>
<cache:annotation-driven/>
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager>
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean>
<property name="name" value="default"/>
</bean>
</set>
</property>
</bean>
</beans>
下面来看看@EnableCaching的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
// 用于设置使用哪种代理方式,默认为基于接口的JDK动态代理(false),
// 设置为true,则使用基于继承的CGLIB动态代理
boolean proxyTargetClass() default false;
// 用于设置切面织入方式(设置面向切面编程的实现方式),
// 默认为使用动态代理的方式织入,当然也可以设置为ASPECTJ的方式来实现AOP
AdviceMode mode() default AdviceMode.PROXY;
// 用于设置在一个切点存在多个通知的时候各个通知的执行顺序,默认为最低优先级,
// 其中数字却大优先级越低,这里默认为最低优先级,int LOWEST_PRECEDENCE =
// Integer.MAX_VALUE;,却是整数的最大值
int order() default Ordered.LOWEST_PRECEDENCE;
}
public enum AdviceMode {
PROXY,
ASPECTJ
}
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
int getOrder();
}
由上面的源码可以看出,缓存功能是依靠AOP来实现的。
2.2 @Cacheable
该注解用于标注于方法之上用于标识该方法的返回结果需要被缓存起来,标注于类之上标识该类中所有方法均需要将结果缓存起来。
该注解标注的方法每次被调用前都会触发缓存校验,校验指定参数的缓存是否已存在(已发生过相同参数的调用),若存在,直接返回缓存结果,否则执行方法内容,最后将方法执行结果保存到缓存中。
2.2.1 使用
@Service
@Log4j2
public class AnimalService {
@Autowired
private AnimalRepository animalRepository;
//...
// @Cacheable("animalById")
@Cacheable(value = "animalById", key = "#id")
public ResponseEntity<Animal> getAnimalById(final int id){
return ResponseEntity.ok(animalRepository.selectById(id));
}
//...
}
上面的实例中两个@Cacheable配置效果其实是一样的,其中value指定的缓存的名称,它和另一个方法cacheName效果一样,一般来说这个缓存名称必须要有,因为这个是区别于其他方法的缓存的唯一方法。
这里我们介绍一下缓存的简单结构,在缓存中,每个这样的缓存名称的名下都会存在着多个缓存条目,这些缓存条目对应在使用不同的参数调用当前方法时生成的缓存,所有一个缓存名称并不是一个缓存,而是一系列缓存。
另一个key用于指定当前方法的缓存保存时的键的组合方式,默认的情况下使用所有的参数组合而成,这样可以有效区分不同参数的缓存。当然我们也可以手动指定,指定的方法是使用SPEL表达式。
这里我么来简单看看其源码,了解下其他几个方法的作用:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
// 用于指定缓存名称,与cacheNames()方法效果一致
@AliasFor("cacheNames")
String[] value() default {};
// 用于指定缓存名称,与value()方法效果一致
@AliasFor("value")
String[] cacheNames() default {};
// 用于使用SPEL手动指定缓存键的组合方式,默认情况使用所有的参数来组合成键,除非自定义了keyGenerator。
// 使用SPEL表达式可以根据上下文环境来获取到指定的数据:
// #root.method:用于获取当前方法的Method实例
// #root.target:用于获取当前方法的target实例
// #root.caches:用于获取当前方法关联的缓存
// #root.methodName:用于获取当前方法的名称
// #root.targetClass:用于获取目标类类型
// #root.args[1]:获取当前方法的第二个参数,等同于:#p1和#a1和#argumentName
String key() default "";
// 自定义键生成器,定义了该方法之后,上面的key方法自动失效,这个键生成器是:
// org.springframework.cache.interceptor.KeyGenerator,这是一个函数式接口,
// 只有一个generate方法,我们可以通过自定义的逻辑来实现自定义的key生成策略。
String keyGenerator() default "";
// 用于设置自定义的cacheManager(缓存管理器),可以自动生成一个cacheResolver
// (缓存解析器),这一下面的cacheResolver()方法设置互斥
String cacheManager() default "";
// 用于设置一个自定义的缓存解析器
String cacheResolver() default "";
// 用于设置执行缓存的条件,如果条件不满足,方法返回的结果就不会被缓存,默认无条件全部缓存。
// 同样使用SPEL来定义条件,可以使用的获取方式同key方法。
String condition() default "";
// 这个用于禁止缓存功能,如果设置的条件满足,就不执行缓存结果,与上面的condition不同之处在于,
// 该方法执行在当前方法调用结束,结果出来之后,因此,它除了可以使用上面condition所能使用的SPEL
// 表达式之外,还可以使用#result来获取方法的执行结果,亦即可以根据结果的不同来决定是否缓存。
String unless() default "";
// 设置是否对多个针对同一key执行缓存加载的操作的线程进行同步,默认不同步。这个功能需要明确确定所
// 使用的缓存工具支持该功能,否则不要滥用。
boolean sync() default false;
}
如何自定义一个KeyGenerator呢?
public class AnimalKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder("animal-");
sb.append(target.getClass().getSimpleName()).append("-").append(method.getName()).append("-");
for (Object o : params) {
String s = o.toString();
sb.append(s).append("-");
}
return sb.deleteCharAt(sb.lastIndexOf("-")).toString();
}
}
2.3 @CachePut
该注解用于更新缓存,无论结果是否已经缓存,都会在方法执行结束插入缓存,相当于更新缓存。一般用于更新方法之上。
@Service
@Log4j2
public class AnimalService {
@Autowired
private AnimalRepository animalRepository;
//...
@CachePut(value = "animalById", key = "#animal.id")
public ResponseEntity<Animal> updateAnimal(final Animal animal){
Wrapper<Animal> animalWrapper = new UpdateWrapper<>();
((UpdateWrapper<Animal>) animalWrapper).eq("id",animal.getId());
animalRepository.update(animal, animalWrapper);
return ResponseEntity.ok(this.getAnimalById(animal.getId()));
}
//...
}
这里指定更新缓存,value同样还是缓存名称,这里更新的是上面查询操作的同一缓存,而且key设置为id也与上面的key设置对应。
如此设置之后,每次执行update方法时都会直接执行方法内容,然后将返回的结果保存到缓存中,如果存在相同的key,直接替换缓存内容执行缓存更新。
下面来看看源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CachePut {
// 同上
@AliasFor("cacheNames")
String[] value() default {};
// 同上
@AliasFor("value")
String[] cacheNames() default {};
// 同上
String key() default "";
// 同上
String keyGenerator() default "";
// 同上
String cacheManager() default "";
// 同上
String cacheResolver() default "";
// 同上
String condition() default "";
// 同上
String unless() default "";
}
只有一点要注意:这里的设置一定要和执行缓存保存的方法的@Cacheable的设置一致,否则无法准确更新。
2.4 @CacheEvict
该注解主要用于删除缓存操作。
@Service
@Log4j2
public class AnimalService {
@Autowired
private AnimalRepository animalRepository;
//...
@CacheEvict(value = "animalById", key = "#id")
public ResponseEntity<Integer> deleteAnimalById(final int id){
return ResponseEntity.ok(animalRepository.deleteById(id));
}
//...
}
简单明了,看看源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
// 同上
@AliasFor("cacheNames")
String[] value() default {};
// 同上
@AliasFor("value")
String[] cacheNames() default {};
// 同上
String key() default "";
// 同上
String keyGenerator() default "";
// 同上
String cacheManager() default "";
// 同上
String cacheResolver() default "";
// 同上
String condition() default "";
// 这个设置用于指定当前缓存名称名下的所有缓存是否全部删除,默认false。
boolean allEntries() default false;
// 这个用于指定删除缓存的操作是否在方法调用之前完成,默认为false,表示先调用方法,在执行缓存删除。
boolean beforeInvocation() default false;
}
2.5 @Caching
这个注解用于组个多个缓存操作,包括针对不用缓存名称的相同操作等,源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
// 用于指定多个缓存设置操作
Cacheable[] cacheable() default {};
// 用于指定多个缓存更新操作
CachePut[] put() default {};
// 用于指定多个缓存失效操作
CacheEvict[] evict() default {};
}
简单用法:
@Service
@Log4j2
public class AnimalService {
@Autowired
private AnimalRepository animalRepository;
//...
@Caching(
evict = {
@CacheEvict(value = "animalById", key = "#id"),
@CacheEvict(value = "animals", allEntries = true, beforeInvocation = true)
}
)
public ResponseEntity<Integer> deleteAnimalById(final int id){
return ResponseEntity.ok(animalRepository.deleteById(id));
}
@Cacheable("animals")
public ResponseEntity<Page<Animal>> getAnimalPage(final Animal animal, final int pageId, final int pageSize){
Page<Animal> page = new Page<>();
page.setCurrent(pageId);
page.setSize(pageSize);
return ResponseEntity.ok((Page<Animal>) animalRepository.selectPage(page,packWrapper(animal, WrapperType.QUERY)));
}
//...
}
2.6 @CacheConfig
该注解标注于类之上,用于进行一些公共的缓存相关配置。源码为:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
// 设置统一的缓存名,适用于整个类中的方法全部是针对同一缓存名操作的情况
String[] cacheNames() default {};
// 设置统一个键生成器,免去了每个缓存设置中单独设置
String keyGenerator() default "";
// 设置统一个自定义缓存管理器
String cacheManager() default "";
// 设置统一个自定义缓存解析器
String cacheResolver() default "";
}
SpringBoot基础系列-SpringCache使用的更多相关文章
- SpringBoot基础系列-使用日志
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996897.html SpringBoot基础系列-使用日志 概述 SpringBoot ...
- SpringBoot基础系列-使用Profiles
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996884.html SpringBoot基础系列-使用Profile 概述 Profi ...
- SpringBoot基础系列-SpringBoot配置
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9990680.html SpringBoot基础系列-SpringBoot配置 概述 属性 ...
- 【SpringBoot 基础系列】实现一个自定义配置加载器(应用篇)
[SpringBoot 基础系列]实现一个自定义配置加载器(应用篇) Spring 中提供了@Value注解,用来绑定配置,可以实现从配置文件中,读取对应的配置并赋值给成员变量:某些时候,我们的配置可 ...
- 【SpringBoot基础系列】手把手实现国际化支持实例开发
[SpringBoot基础系列]手把手实现国际化支持实例开发 国际化的支持,对于app开发的小伙伴来说应该比价常见了:作为java后端的小伙伴,一般来讲接触国际化的机会不太多,毕竟业务开展到海外的企业 ...
- SpringBoot基础系列之自定义配置源使用姿势实例演示
[SpringBoot基础系列]自定义配置源的使用姿势介绍 前面一篇博文介绍了一个@Value的一些知识点,其中提了一个点,@Value对应的配置,除了是配置文件中之外,可以从其他的数据源中获取么,如 ...
- 【SpringBoot基础系列-实战】如何指定 bean 最先加载(应用篇)
[基础系列-实战]如何指定 bean 最先加载(应用篇) 在日常的业务开发中,绝大多数我们都是不关注 bean 的加载顺序,然而如果在某些场景下,当我们希望某个 bean 优于其他的 bean 被实例 ...
- SpringBoot基础系列一
SpringBoot基础知识概览 特性 核心理念:约定优于配置 特点: 1. 开箱即用,根据项目依赖自动配置 2. 功能强大的服务体系,如嵌入式服务.安全 3. 绝无代码生成,不用写.xml配置,用注 ...
- springBoot基础系列--properties配置
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/7183408.html SpringBoot中免除了大部分手动配置,但是对于一些特定的情况, ...
随机推荐
- window.history.back(-1);与window.go(-1);的区别
history.back(-1):直接返回当前页的上一页,数据全部消息,是个新页面 history.go(-1):也是返回当前页的上一页,不过表单里的数据全部还在 history.back(1) 前进 ...
- ES 13 - Elasticsearch的元字段 (_index、_type、_source、_routing等)
目录 1 标识元字段 1.1 _index - 文档所属的索引 1.2 _uid - 包含_type和_id的复合字段 1.3 _type - 文档的类型 1.4 _id - 文档的id 2 文档来源 ...
- 目标检测之YOLO V2 V3
YOLO V2 YOLO V2是在YOLO的基础上,融合了其他一些网络结构的特性(比如:Faster R-CNN的Anchor,GooLeNet的\(1\times1\)卷积核等),进行的升级.其目的 ...
- python3的变量作用域规则和nonlocal关键字
也许你已经觉得自己可以熟练使用python并能胜任许多开发任务,所以这篇文章是在浪费你的时间.不过别着急,我们先从一个例子开始: i = 0 def f(): print(i) i += 1 prin ...
- .NET Core:依赖注入
在Startup的ConfigureServices方法中加入需要依赖注入的东西. 每次从容器 中获取的时候都是一个新的实例:services.AddTransient<ITransient ...
- 人生苦短,我用 Python
从2015开始国内就开始慢慢接触Python了,从16年开始Python就已经在国内的热度更高了,目前也可以算的上"全民Python"了.众所周知小学生的教材里面已经有Python ...
- 不一样的 SQL Server 日期格式化
不一样的 SQL Server 日期格式化 Intro 最近统计一些数据,需要按天/按小时/按分钟来统计,涉及到一些日期的格式化,网上看了一些文章大部分都是使用 CONVERT 来转换的,SQL Se ...
- 为什么在STM32F429工程配置中需要预先定义USE_STDPERIPH_DRIVER和STM32F429_439xx?
如图: 1.查找USE_STDPERIPH_DRIVER,发现这个宏出现在stm32f4xx.h头文件中,并且有如下代码: 也就是说,通过已经定义了USE_STDPERIPH_DRIVER宏加载stm ...
- Windows Server 2016-图形化新建域用户(一)
上章节我们介绍了有关OU组织单位的日常管理,本章我们将对域用户的创建进行简单介绍,常规的操作方法是通过管理控制台图形化手工创建,具体操作方法如下: 1.常规管理控制台 Active Directory ...
- Luogu P5284 [十二省联考2019]字符串问题
好难写的字符串+数据结构问题,写+调了一下午的说 首先理解题意后我们对问题进行转化,对于每个字符串我们用一个点来代表它们,其中\(A\)类串的点权为它们的长度,\(B\)类串的权值为\(0\) 这样我 ...