Spring系列第40篇:缓存使用(@EnableCaching、@Cacheable

@Test

public void test3() {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

context.register(MainConfig1.class);

context.refresh();

ArticleService articleService = context.getBean(ArticleService.class);

System.out.println(articleService.getById(1L, true));

System.out.println(articleService.getById(1L, true));

System.out.println(articleService.getById(1L, false));

System.out.println(articleService.getById(1L, true));

}

运行输出

获取数据!

spring缓存:27e7c11a-26ed-4c8b-8444-78257daafed5

spring缓存:27e7c11a-26ed-4c8b-8444-78257daafed5

获取数据!

spring缓存:05ff7612-29cb-4863-b8bf-d1b7c2c192b7

spring缓存:27e7c11a-26ed-4c8b-8444-78257daafed5

从输出中可以看出,第1次和第3次,都进到方法里面去了,而2和4走了缓存,第一次执行完毕之后结果被丢到了缓存中,所以2和4这2次获取的结果和第1次是一样的。

unless属性:控制是否需要将结果丢到缓存中

用于否决方法缓存的SpEL表达式。 与condition不同,此表达式是在调用方法后计算的,因此可以引用结果。 默认值为“”,这意味着缓存永远不会被否决。

前提是condition为空或者为true的情况下,unless才有效,condition为false的时候,unless无效,unless为true,方法返回结果不会丢到缓存中;unless为false,方法返回结果会丢到缓存中。

其值spel的写法和key属性类似。

案例4

下面来个案例,当返回结果为null的时候,不要将结果进行缓存,ArticleService添加下面代码

Map<Long, String> articleMap = new HashMap<>();

/**

* 获取文章,先从缓存中获取,如果获取的结果为空,不要将结果放在缓存中

* @param id

* @return

*/

@Cacheable(cacheNames = “cache1”, key = “‘findById’+#id”, unless = “#result==null”)

public String findById(Long id) {

this.articleMap.put(1L, “spring系列”);

System.out.println(“----获取文章:” + id);

return articleMap.get(id);

}

来个测试用例,4次调用findById,前面2次有数据,后面2次返回null,并将缓存中的key打印了出来

@Test

public void test4() {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

context.register(MainConfig1.class);

context.refresh();

ArticleService articleService = context.getBean(ArticleService.class);

System.out.println(articleService.findById(1L));

System.out.println(articleService.findById(1L));

System.out.println(articleService.findById(3L));

System.out.println(articleService.findById(3L));

{

System.out.println(“下面打印出缓cache1缓存中的key列表”);

ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);

ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache(“cache1”);

cache1.getNativeCache().keySet().stream().forEach(System.out::println);

}

}

运行输出

----获取文章:1

spring系列

spring系列

----获取文章:3

null

----获取文章:3

null

下面打印出缓cache1缓存中的key列表

findById1

可以看出文章id为1的结果被缓存了,文件id为3的没有被缓存。

condition和unless对比

缓存的使用过程中有2个点:

  1. 查询缓存中是否有数据

  2. 如果缓存中没有数据,则去执行目标方法,然后将方法结果丢到缓存中。

spring中通过condition和unless对这2点进行干预。

condition作用域上面2个过程,当为true的时候,会尝试从缓存中获取数据,如果没有,会执行方法,然后将方法返回值丢到缓存中;如果为false,则直接调用目标方法,并且结果不会放在缓存中。

而unless在condition为true的情况下才有效,用来判断上面第2点中,是否不要将结果丢到缓存中,如果为true,则结果不会丢到缓存中,如果为false,则结果会丢到缓存中,并且unless中可以使用spel表达式通过#result来获取方法返回值。

@CachePut:将结果放入缓存


作用

@CachePut也可以标注在类或者方法上,被标注的方法每次都会被调用,然后方法执行完毕之后,会将方法结果丢到缓存中;当标注在类上,相当于在类的所有方法上标注了@CachePut。

有3种情况,结果不会丢到缓存

  1. 当方法向外抛出的时候

  2. condition的计算结果为false的时候

  3. unless的计算结果为true的时候

源码和Cacheable类似,包含的参数类似的。

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Inherited

@Documented

public @interface CachePut {

String[] value() default {};

String[] cacheNames() default {};

String key() default “”;

String condition() default “”;

String unless() default “”;

}

  • value和cacheNames:用来指定缓存名称,可以指定多个

  • key:缓存的key,spel表达式,写法参考@Cacheable中的key

  • condition:spel表达式,写法和@Cacheable中的condition一样,当为空或者计算结果为true的时候,方法的返回值才会丢到缓存中;否则结果不会丢到缓存中

  • unless:当condition为空或者计算结果为true的时候,unless才会起效;true:结果不会被丢到缓存,false:结果会被丢到缓存。

案例5

来个案例,实现新增文章的操作,然后将文章丢到缓存中,注意下面@CachePut中的cacheNames、key 2个参数和案例4中findById方法上@Cacheable中的一样,说明他们共用一个缓存,key也是一样的,那么当add方法执行完毕之后,再去调用findById方法,则可以从缓存中直接获取到数据。

@CachePut(cacheNames = “cache1”, key = “‘findById’+#id”)

public String add(Long id, String content) {

System.out.println(“新增文章:” + id);

this.articleMap.put(id, content);

return content;

}

测试用例

@Test

public void test5() {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

context.register(MainConfig1.class);

context.refresh();

ArticleService articleService = context.getBean(ArticleService.class);

//新增3个文章,由于add方法上面有@CachePut注解,所以新增之后会自动丢到缓存中

articleService.add(1L, “java高并发系列”);

articleService.add(2L, “Maven高手系列”);

articleService.add(3L, “MySQL高手系列”);

//然后调用findById获取,看看是否会走缓存

System.out.println(“调用findById方法,会尝试从缓存中获取”);

System.out.println(articleService.findById(1L));

System.out.println(articleService.findById(2L));

System.out.println(articleService.findById(3L));

{

System.out.println(“下面打印出cache1缓存中的key列表”);

ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);

ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache(“cache1”);

cache1.getNativeCache().keySet().stream().forEach(System.out::println);

}

}

运行输出

新增文章:1

新增文章:2

新增文章:3

调用findById方法,会尝试从缓存中获取

java高并发系列

Maven高手系列

MySQL高手系列

下面打印出缓cache1缓存中的key列表

findById3

findById2

findById1

看几眼输出结果,然后再来看一下findById方法的代码

@Cacheable(cacheNames = “cache1”, key = “‘findById’+#id”, unless = “#result==null”)

public String findById(Long id) {

this.articleMap.put(1L, “spring系列”);

System.out.println(“----获取文章:” + id);

return articleMap.get(id);

}

输出中并没有----这样的内容,说明调用findById方法获取结果是从缓存中得到的。

@CacheEvict:缓存清理


作用

用来清除缓存的,@CacheEvict也可以标注在类或者方法上,被标注在方法上,则目标方法被调用的时候,会清除指定的缓存;当标注在类上,相当于在类的所有方法上标注了@CacheEvict。

来看一下源码,每个参数的注释大家详细看一下。

public @interface CacheEvict {

/**

* cache的名称,和cacheNames效果一样

*/

String[] value() default {};

/**

* cache的名称,和cacheNames效果一样

*/

String[] cacheNames() default {};

/**

* 缓存的key,写法参考上面@Cacheable注解的key

*/

String key() default “”;

/**

* @CacheEvict 注解生效的条件,值为spel表达式,写法参考上面 @Cacheable注解中的condition

*/

String condition() default “”;

/**

* 是否清理 cacheNames 指定的缓存中的所有缓存信息,默认是false

* 可以将一个cache想象为一个HashMap,当 allEntries 为true的时候,相当于HashMap.clear()

* 当 allEntries 为false的时候,只会干掉key对应的数据,相当于HashMap.remove(key)

*/

boolean allEntries() default false;

/**

* 何事执行清除操作(方法执行前 or 方法执行成功之后)

* true:@CacheEvict 标注的方法执行之前,执行清除操作

* false:@CacheEvict 标注的方法执行成功之后,执行清除操作,当方法弹出异常的时候,不会执行清除操作

*/

boolean beforeInvocation() default false;

}

condition属性

@CacheEvict 注解生效的条件,值为spel表达式,写法参考上面 @Cacheable注解中的condition

会清除哪些缓存?

默认情况下会清除cacheNames指定的缓存中key参数指定的缓存信息。

但是当 allEntries 为true的时候,会清除 cacheNames 指定的缓存中的所有缓存信息。

具体什么时候清除缓存?

这个是通过 beforeInvocation 参数控制的,这个参数默认是false,默认会在目标方法成功执行之后执行清除操作,若方法向外抛出了异常,不会执行清理操作;

如果 beforeInvocation  为true,则方法被执行之前就会执行缓存清理操作,方法执行之后不会再执行了。

案例6

ArticleService中新增个方法,使用@CacheEvict标注,这个方法执行完毕之后,会清理cache1中key=findById+参数id的缓存信息,注意cacheNames和key两个参数的值和findById中这2个参数的值一样。

@CacheEvict(cacheNames = “cache1”, key = “‘findById’+#id”) //@1

public void delete(Long id) {

System.out.println(“删除文章:” + id);

this.articleMap.remove(id);

}

新增测试用例,注释比较清晰,就不解释了

@Test

public void test6() {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

context.register(MainConfig1.class);

context.refresh();

ArticleService articleService = context.getBean(ArticleService.class);

//第1次调用findById,缓存中没有,则调用方法,将结果丢到缓存中

System.out.println(articleService.findById(1L));

//第2次调用findById,缓存中存在,直接从缓存中获取

System.out.println(articleService.findById(1L));

//执行删除操作,delete方法上面有@CacheEvict方法,会清除缓存

articleService.delete(1L);

//再次调用findById方法,发现缓存中没有了,则会调用目标方法

System.out.println(articleService.findById(1L));

}

运行输出

----获取文章:1

spring系列

spring系列

删除文章:1

----获取文章:1

spring系列

调用了3次findById,第1次,缓存中没有,所以进到方法内部了,然后将结果丢到缓存了,第2次缓存中有,所以从缓存获取,然后执行了delete方法,这个方法执行完毕之后,会清除缓存中文章id为1L的文章信息,最后执行第三次findById方法,此时缓存中没有发现数据,然后进到目标方法内部了,目标方法内部输出了----内容。

@Caching:缓存注解组


当我们在类上或者同一个方法上同时使用@Cacheable、@CachePut和@CacheEvic这几个注解中的多个的时候,此时可以使用@Caching这个注解来实现。

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Inherited

@Documented

public @interface Caching {

Cacheable[] cacheable() default {};

CachePut[] put() default {};

CacheEvict[] evict() default {};

}

@CacheConfig:提取公共配置


这个注解标注在类上,可以将其他几个缓存注解(@Cacheable、@CachePut和@CacheEvic)的公共参数给提取出来放在@CacheConfig中。

比如当一个类中有很多方法都需要使用(@Cacheable、@CachePut和@CacheEvic)这些缓存注解的时候,大家可以看一下这3个注解的源码,他们有很多公共的属性,比如:cacheNames、keyGenerator、cacheManager、cacheResolver,若这些属性值都是一样的,可以将其提取出来,放在@CacheConfig中,不过这些注解(@Cacheable、@CachePut和@CacheEvic)中也可以指定属性的值对@CacheConfig中的属性值进行覆盖。

@CacheConfig(cacheNames = “cache1”)

public class ArticleService {

@Cacheable(key = “‘findById’+#id”)

public String findById(Long id) {

this.articleMap.put(1L, “spring系列”);

System.out.println(“----获取文章:” + id);

return articleMap.get(id);

}

}

原理

spring中的缓存主要是利用spring中aop实现的,通过Aop对需要使用缓存的bean创建代理对象,通过代理对象拦截目标方法的执行,实现缓存功能。

重点在于@EnableCaching这个注解,可以从@Import这个注解看起

@Import(CachingConfigurationSelector.class)

public @interface EnableCaching {

}

最终会给需要使用缓存的bean创建代理对象,并且会在代理中添加一个拦截器org.springframework.cache.interceptor.CacheInterceptor,这个类中的invoke方法是关键,会拦截所有缓存相关的目标方法的执行,大家可以去细看一下。

总结

Spring系列到此已经40篇了,能和我一起坚持到现在的,真的不容易。

还没有看完的朋友,建议大家按序都看一遍,文章最好按顺序看,前后知识点是有依赖的。

如果前面的文章都看过了,那么本文的原理,不用我介绍了,大家自己很容易就搞懂了。

案例源码


https://gitee.com/javacode2018/spring-series

路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。

Spring系列


  1. Spring系列第1篇:为何要学spring?

  2. Spring系列第2篇:控制反转(IoC)与依赖注入(DI)

  3. Spring系列第3篇:Spring容器基本使用及原理

  4. Spring系列第4篇:xml中bean定义详解(-)

  5. Spring系列第5篇:创建bean实例这些方式你们都知道?

  6. Spring系列第6篇:玩转bean scope,避免跳坑里!

  7. Spring系列第7篇:依赖注入之手动注入

  8. Spring系列第8篇:自动注入(autowire)详解,高手在于坚持

  9. Spring系列第9篇:depend-on到底是干什么的?

  10. Spring系列第10篇:primary可以解决什么问题?

  11. Spring系列第11篇:bean中的autowire-candidate又是干什么的?

  12. Spring系列第12篇:lazy-init:bean延迟初始化

  13. Spring系列第13篇:使用继承简化bean配置(abstract & parent)

  14. Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?

  15. Spring系列第15篇:代理详解(Java动态代理&cglib代理)?

  16. Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)

  17. Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)

  18. Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)

  19. Spring系列第18篇:@import详解(bean批量注册)

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值