Spring Cache入门使用,注解属性使用说明,service,方法自调用缓存失效问题解决。

写在前面

解决同一个类中,方法自调用(this)会导致注解标签失效的解决方法,下面文档中有,但是不够方便,科学,所以找到了切换代理模式的方法来解决此问题。步骤如下

  1. 项目是spring boot项目
  2. pom中加入依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
</dependency>
  1. pom文件中加入插件,如下
<plugin>
     <groupId>org.codehaus.mojo</groupId>
     <artifactId>aspectj-maven-plugin</artifactId>
     <version>1.4</version>
     <configuration>
         <showWeaveInfo>true</showWeaveInfo>
         <source>1.8</source>
         <target>1.8</target>
         <Xlint>ignore</Xlint>
         <complianceLevel>1.8</complianceLevel>
         <encoding>UTF-8</encoding>
         <verbose>false</verbose>
         <aspectLibraries>
             <aspectLibrary>
                 <groupId>org.springframework</groupId>
                 <artifactId>spring-aspects</artifactId>
             </aspectLibrary>
         </aspectLibraries>
     </configuration>
     <executions>
         <execution>
             <goals>
                 <goal>compile</goal>
                 <goal>test-compile</goal>
             </goals>
         </execution>
     </executions>
     <dependencies>
         <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjrt</artifactId>
             <version>${aspectj.version}</version>
         </dependency>
         <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjtools</artifactId>
             <version>${aspectj.version}</version>
         </dependency>
     </dependencies>
 </plugin>
  1. 启动类上加入注解
  2. @EnableCaching(mode = AdviceMode.ASPECTJ)
    上面那几步做好之后,就解决了同类方法的相互想用问题了,但是如果要在idea里面启动项目,需要先在命令行里面通过mvn clean package -DskipTests编译代码之后,idea里面启动项目才会生效。
    如果直接打包运行就已经是生效的状态了

Spring Cache

在这里插入图片描述

使用方法

1

  • @EnableCaching,在配置类上加入此注解,或者SpringBoot项目的*Application类上

2

  • 配置文件中配置缓存模式(Spring.cache.type=xxx)

3

  • 需要缓存的方法上加上注解(xml方式请看官方文档)

缓存注解

Cacheable

  • 执行结果添加到缓存中

    • String[] cacheNames() default {};
      
      • 缓存的名称,一个完整的缓存记录是由cacheName+key的参数值拼接而成。

        • 举例

          • @Cacheable(value = “book”,key = “#bookId”),那么生成的缓存键值对的键名=book:1(如果执行了该方法,通过id为1调用的该方法,那么执行完成之后就会产生这条缓存记录)。如果下次还是使用id=1来执行这个方法,那么框架会把参数值和缓存名称拼接起来,book:1是拼好的缓存全名,通过这个去查有没有这条缓存记录,如果有,直接把结果返回方法调用者,不用再执行真实方法,如果传递的参数是2,那么拼出来的缓存名称是book:2,没有这条记录,会执行真实的业务方法,执行成功之后会缓存执行结果并把结果返回给方法调用者
    • String key() default "";
      
      • 缓存后面拼接的参数,key代表需要在缓存后面拼上那个参数的值
    • String keyGenerator() default "";
      
      • 指定key的生成器,不再手动指定缓存key(缓存名称后面拼的参数)

        • 说明

          • keyGenerator和key是互斥的,一个注解中只能选择这两个属性中的一个,如果都用,会报错
    • String cacheManager() default "";
      
    • String cacheResolver() default "";
      
    • String condition() default "";
      
      • 真实方法执行之前通过spEL表达式的结果(true/false)判断是否要执行

        • 举例

          • @Cacheable(cacheNames=“book”, condition=“#name.length() < 32”)
    • String unless() default "";
      
      • 真实方法执行完成之后,通过这里面的spEL表达式(true/false)判断是否缓存当前执行结果
    • boolean sync() default false;
      
      • 多线程情况下(并发访问),多个线程同时要执行真实方法,通过加锁控制只有一个线程执行真实方法,其它线程等待

CacheEvict

  • 清除指定缓存

    • String[] cacheNames() default {};
      
      • 同cacheable
    • String key() default "";
      
      • 同cacheable
    • String keyGenerator() default "";
      
      • 同cacheable
    • String cacheManager() default "";
      
      • 同cacheable
    • String cacheResolver() default "";
      
      • 同cacheable
    • String condition() default "";
      
      • 满足条件之后清除缓存
    • boolean allEntries() default false;
      
      • 清除cacheNames的所有缓存,忽略参数

        • 说明

          • 例如book:1,book:2,cacheNames是book,这里如果为true,book开头的缓存都会被清除。如果这里是false,key=1,那么只会清除book:1的缓存
    • boolean beforeInvocation() default false;
      
      • 在方法执行之前清除缓存还是在方法执行之后清除缓存

CachePut

  • 更新缓存

    • String[] cacheNames() default {};
      
      • 同cacheable
    • String key() default "";
      
      • 同cacheable
    • String keyGenerator() default "";
      
      • 同cacheable
    • String cacheManager() default "";
      
      • 同cacheable
    • String cacheResolver() default "";
      
      • 同cacheable
    • String condition() default "";
      
      • 同cacheable
    • String unless() default "";
      
      • 否定将值添加到缓存中。与condition不同,unless表达式是在调用方法后求值,满足unless中的条件,缓存将不会更新
  • 说明

    • 例如执行一个update方法,update上的注解是@CachePut(cacheNames=“book”,key=“#id”),那么方法执行完成之后,框架将会将update方法的返回值更新到book:1的缓存中,不用再等@CacheEvict(cacheNames=“book”,key=“#id”)标注的方法执行且被@Cacheable(cacheNames=“book”,key=“#id”)标注的方法执行
    • 这个注解的作用/效果/意义,约等于执行了@CacheEvict和@Cacheable标注的两个方法(一般情况下是这样)的效果

Caching

  • 配置多个缓存注解

    • Cacheable[] cacheable() default {};

      • 配置多个Cacheable注解
    • CachePut[] put() default {};
      
      • 配置多个CachePut注解
    • CacheEvict[] evict() default {};
      
      • 配置多个CacheEvict注解
  • 说明

    • 要配置多个缓存注解的时候使用,应用于一个方法的多个缓存操作。
  • 举例

    • @Caching(evict = { @CacheEvict(“primary”), @CacheEvict(cacheNames=“secondary”, key=“#p0”) })

使用注意

缓存失效

  • 原因:同类方法互相调用

    • 如果在同一个类中的a方法调用b方法,就算b方法上有缓存注解,也不会有任何效果。所以,如果要让缓存注解生效,要避免同类中的方法相互调用(如果想让缓存生效的前提下)。其实调用也没什么,只是每次都会执行真实的方法,不会有缓存的效果
  • 解决:

    • 1

      • 当前类注入自己的实例,使用注入的实例代替this来调用同类方法

        • 缺点:

          • 自己注入自己,感觉那里不对,有循环注入的风险
    • 2

      • 使用AopContent.currentProxy()获取当前类实例,然后强转为当前类实例,使用强转之后的对象代替this调用同类方法

        • 使用前提

          • @EnableAspectJAutoProxy(exposeProxy = true)
        • 缺点:

          • 业务和AOP绑定到一起了,如果后面不用缓存,或者切换代理模式了,还需要修改这里的代码
    • 3

      • 切换代理模式,使用Aspectj
    • 4

      • 单独开发一个类来做对应缓存方法的缓存业务,相当于使用代理模式,手动编码。如果UserService上有缓存,新建一个UserCacheServiceImpl来调用UserServiceImpl的方法,缓存注解打到UserCacheServiceImpl上

        • 缺点:

          • 单独开发类,增加工作量
        • 优点:

          • 不会出现缓存失效的问题,业务隔离,不会因为缓存业务产生额外的耦合
  • 官方文档

    • 谷歌翻译

      • 方法可见性和缓存注释
      • 使用代理时,应仅将缓存注释应用于具有公共可见性的方法。如果使用这些注释对受保护的,私有的或程序包可见的方法进行注释,则不会引发任何错误,但是带注释的方法不会显示已配置的缓存设置。如果您需要注释非公共方法,请考虑使用AspectJ(请参阅本节的其余部分),因为它会更改字节码本身。
      • Spring建议您仅使用注释对具体类(以及具体类的方法)进行@Cache注释,而不是对接口进行注释。您当然可以将@Cache注释放置在接口(或接口方法)上,但这仅在您使用基于接口的代理时才可以预期地起作用。Java批注不是从接口继承的事实意味着,如果您使用基于类的代理(proxy-target-class=“true”)或基于编织的方面(mode=“aspectj”),则缓存设置不会被代理和编织基础结构识别,并且对象也不会被包装在缓存代理中。
      • 在代理模式(默认)下,仅拦截通过代理传入的外部方法调用。这意味着自调用(实际上是目标对象中的一个方法调用了目标对象的另一种方法)即使在调用的方法上标有,也不会在运行时导致实际的缓存@Cacheable。aspectj在这种情况下,请考虑使用该模式。另外,必须完全初始化代理以提供预期的行为,因此您不应在初始化代码(即@PostConstruct)中依赖此功能。
    • 原版

      • Method visibility and cache annotations
      • When you use proxies, you should apply the cache annotations only to methods with public visibility. If you do annotate protected, private, or package-visible methods with these annotations, no error is raised, but the annotated method does not exhibit the configured caching settings. Consider using AspectJ (see the rest of this section) if you need to annotate non-public methods, as it changes the bytecode itself.
      • Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Cache* annotation, as opposed to annotating interfaces. You certainly can place the @Cache* annotation on an interface (or an interface method), but this works only as you would expect it to if you use interface-based proxies. The fact that Java annotations are not inherited from interfaces means that, if you use class-based proxies (proxy-target-class=“true”) or the weaving-based aspect (mode=“aspectj”), the caching settings are not recognized by the proxying and weaving infrastructure, and the object is not wrapped in a caching proxy.
      • In proxy mode (the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object that calls another method of the target object) does not lead to actual caching at runtime even if the invoked method is marked with @Cacheable. Consider using the aspectj mode in this case. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, @PostConstruct).

便捷用法

  • 如果重复的标签内容,可以这样使用

    • 之前

      • @Cacheable(cacheNames=“books”, key=“#isbn”)
        public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
    • 之后

      • @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.METHOD})
        @Cacheable(cacheNames=“books”, key=“#isbn”)
        public @interface SlowService {
        }
      • @SlowService
        public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

spring cache 官方文档地址 https://docs.spring.io/spring-framework/docs/5.3.9/reference/html/integration.html#spring-integration

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值