1.请参见官方文档Spring指南之使用 Spring 缓存数据
2.请参见Spring官方文档之缓存抽象
3.参见github代码

一.简介

本指南将带领您完成在Spring托管bean上启用缓存的过程。

二.你将创造什么(What You Will Build)

您将构建一个应用程序,在一个简单的book repository上启用缓存。

三.创建项目

创建spring boot项目
单击Dependencies并选择Spring cache abstraction、Spring Web。

3.1 创建book实体类、service以及实现类以及controller

3.2 启用缓存

1.在启动类上添加@EnableCaching注解。@EnableCaching注解触发一个后处理器(post-processor),它检查每个Spring bean在公共方法(public methods)上是否存在缓存注解。如果找到这样的注解,就会自动创建一个代理来拦截方法调用,并相应地处理缓存行为。后处理器处理@Cacheable, @CachePut和@CacheEvict注解。Spring Boot会自动配置一个合适的CacheManager来作为相关缓存的提供者。我们的示例没有使用特定的缓存库,因此我们的缓存存储是使用ConcurrentHashMap。缓存抽象支持广泛的缓存库,并且完全兼容JSR-107 (JCache)

在这里插入图片描述

2.在BookServiceImpl上添加相关缓存注解

    /**
     * unless: 如果结果的大小为0,方法的结果将不会被缓存。如果我们不提供键,请参见官方文档默认的key生成
     */
    @Cacheable(cacheNames = "allBookCache")
    @Override
    public List<Book> findAll() {
        return books;
    }

    /**
     * 自定义key
     */
    @Cacheable(cacheNames = "bookCache", key = "#id")
    @Override
    public Book getById(Integer id) {
        for (Book book : books) {
            if (book.getId() == id) {
                return book;
            }
        }
        return null;
    }

    /**
     * 新增(增删改时都要清除缓存!!!!)
     * 如果allEntries=true,则缓存中的所有条目都将被删除
     * 这里注意返回值类型-->Book(不然@CachePut会报错:Cache 'bookCache' does not allow 'null' values.)
     */
    @Caching(
            put = {@CachePut(value = "bookCache", key = "#result.id")},
            evict = {@CacheEvict(value = "allBookCache", allEntries = true)}
    )
    @Override
    public Book addBook(String name) {
        Book book = new Book(counter.incrementAndGet(), name);
        books.add(book);
        return book;
    }

四.使用postman测试

五.缓存抽象讲解

5.1 理解缓存抽象

1.与Spring框架中的其他服务一样,缓存服务是一个抽象(不是缓存实现),需要使用实际的存储来存储缓存数据——也就是说,抽象使你不必编写缓存逻辑但不提供实际的数据存储。这个抽象由org.springframework.cache.Cache和org.springframework.cache.CacheManager接口实现。Spring提供了一些这种抽象的实现:JDK java.util.concurrent.ConcurrentMap based caches, Ehcache 2.x, Gemfire cache, Caffeine, and JSR-107 compliant caches (such as Ehcache 3.x)。有关插入其他缓存存储和提供程序的更多信息,请参见插入不同的后端缓存。

2.缓存抽象对多线程和多进程环境没有特殊处理,因为这些功能由缓存实现处理。

3.要使用缓存抽象,你需要注意两个方面:

  • 缓存声明:确定需要缓存的方法及其策略。
  • 缓存配置:用于存储和读取数据的后备缓存。
5.2 @Cacheable详解
5.2.1 @Cacheable注解

1.方法的结果存储在缓存中,这样,在后续调用(具有相同的参数)时,缓存中的值就会被返回,而不必实际调用该方法。注释声明需要与注释方法关联的缓存的名称,比如下:findBook方法与名为books的缓存相关联。每次调用该方法时,都会检查缓存,以查看调用是否已经运行,并且不必重复。虽然在大多数情况下,只声明一个缓存,但注释允许指定多个名称,以便使用多个缓存。在这种情况下,在调用方法之前检查每个缓存—如果至少击中一个缓存,则返回相关的值。
在这里插入图片描述

5.2.2 默认的key生成

因为缓存本质上是键值存储,所以每次调用缓存的方法都需要转换成合适的键来进行缓存访问。缓存抽象使用了一个基于以下算法的简单KeyGenerator:

  • 如果没有给出参数,则返回SimpleKey.EMPTY。
  • 如果只给出一个参数,则返回该实例。
  • 如果给出多个参数,则返回包含所有参数的SimpleKey。
    在这里插入图片描述
5.2.3 自定义key生成声明

@Cacheable注释允许您指定如何通过键属性生成键。您可以使用SpEL选择感兴趣的参数(或它们的嵌套属性)、执行操作,甚至调用任意方法,而无需编写任何代码或实现任何接口。
在这里插入图片描述

5.2.4 条件缓存

1.有时,方法可能并不总是适合缓存(例如,它可能依赖于给定的参数)。缓存注释通过条件参数支持这样的用例,条件参数接受一个SpEL表达式,该表达式的值要么为真,要么为假。如果为true,则缓存该方法。如果没有,它的行为就好像该方法没有被缓存一样(也就是说,无论缓存中有什么值或使用了什么参数,每次都会调用该方法)。例如,只有当参数名的长度小于32时,以下方法才会被缓存:
在这里插入图片描述
2.除了 condition 参数外,还可以使用 unless 参数来否决向缓存添加值。与 condition 不同,unless 表达式在调用方法后求值
在这里插入图片描述

3.缓存抽象支持 java.util.Optional返回类型。如果存在可选值,它将被存储在相关的缓存中。如果一个可选值不存在,null将被存储在相关的缓存中。#result总是指向业务实体,而不是受支持的包装器。注意,#result仍然指向Book而不是Optional。因为它可能为空,所以我们使用SpEL的安全导航操作符。
在这里插入图片描述

5.2.5 同步缓存

在多线程环境中,可能会为同一个参数并发调用某些操作(通常是在启动时)。默认情况下,缓存抽象不锁定任何东西,相同的值可能会被计算多次,这违背了缓存的目的。对于这些特殊情况,可以使用sync属性指示底层缓存提供程序在计算值时锁定缓存条目。因此,只有一个线程忙于计算值,而其他线程则被阻塞,直到条目在缓存中被更新。下面的例子展示了如何使用sync属性:
在这里插入图片描述

5.2.6 Default Cache Resolution(默认的缓存解决方案)

1.缓存抽象使用了一个简单的CacheResolver,通过使用配置的CacheManager来检索在操作级定义的缓存。要提供一个不同的默认缓存解析器,您需要实现org.springframework.cache.interceptor.CacheResolver接口。

5.2.7 Available Caching SpEL Evaluation Context(可用的缓存SpEL评估上下文)

省略,请参看官方文档

5.2.8 Enabling Caching Annotations(启用缓存注释)

1.要启用缓存注解,在你的@Configuration类中添加注解@EnableCaching。

在这里插入图片描述

2.cache:annotation-driven元素和@EnableCaching-annotation都允许您指定影响通过AOP将缓存行为添加到应用程序的方式的各种选项。该配置有意与@Transactional的配置类似。

3.处理缓存注释的默认通知模式是代理,它只允许通过代理拦截调用。同一类内的本地调用不能通过这种方式被拦截。对于更高级的拦截模式,可以考虑结合编译时或加载时编织切换到aspectj模式

4.使用代理时,你应该只将缓存注释应用于具有公共可见性的方法。如果使用这些注释注释受保护的、私有的或包可见的方法,则不会引发错误,但注释的方法不会显示已配置的缓存设置。如果需要注释非公共方法,可以考虑使用AspectJ,因为它会更改字节码本身

5.Spring建议您只使用@Cache注释注释具体类(以及具体类的方法),而不是注释接口。您当然可以在接口(或接口方法)上放置@Cache注释,但这只有在使用代理模式(mode=“proxy”)时才有效。如果使用基于编织的方面(mode=“aspectj”),编织基础设施在接口级声明上无法识别缓存设置。

6.在代理模式(默认)中,只有通过代理进入的外部方法调用才会被拦截。这意味着自调用(实际上,目标对象中的方法调用目标对象的另一个方法)不会导致运行时的实际缓存,即使被调用的方法被标记为@Cacheable。在这种情况下,考虑使用aspectj模式。此外,必须完全初始化代理以提供预期的行为,因此您不应该在初始化代码(即@PostConstruct)中依赖此特性。

5.3 @CachePut详解

1.当需要在不干扰方法执行的情况下更新缓存时,可以使用@CachePut注释。也就是说,该方法总是被调用,其结果被放置到缓存中(根据@CachePut选项)。它支持与@Cacheable相同的选项,应该用于缓存填充而不是方法流优化。

2.在同一个方法上使用@CachePut和@Cacheable注释通常是非常不鼓励的,因为它们有不同的行为。

5.4 @CacheEvict详解

1.缓存抽象不仅允许填充缓存存储,还允许驱逐。这个过程对于从缓存中删除过时或未使用的数据非常有用。使用allEntries属性从缓存中删除所有条目。
在这里插入图片描述

2.请注意,void方法可以与@CacheEvict一起使用——因为这些方法充当触发器,返回值将被忽略(因为它们不与缓存交互)。这不是@Cacheable的情况,它向缓存中添加数据或更新缓存中的数据,因此,需要一个结果

5.5 @Caching详解

1.有时,需要指定同一类型的多个注释(例如@CacheEvict或@CachePut)——例如,因为不同缓存之间的 condition 或 key 表达式不同。@Caching允许在同一方法上使用多个嵌套的@Cacheable、@CachePut和@CacheEvict注释。下面的例子使用了两个@CacheEvict注解:
在这里插入图片描述

5.6 Configuring the Cache Storage(配置缓存储存)

缓存抽象提供了几个存储集成选项。要使用它们,需要声明一个适当的CacheManager(一个控制和管理Cache实例的实体,可以用来检索这些实例以供存储)

5.6.1 JDK ConcurrentMap-based Cache

1.基于jdk的Cache实现位于org.springframework.cache.concurrent包中。它允许你使用ConcurrentHashMap作为后台缓存存储。下面的例子展示了如何配置两个缓存:
在这里插入图片描述

5.6.2 Ehcache-based Cache

1.Ehcache 3.x完全兼容JSR-107,不需要专门的支持。

2.Ehcache 2.x实现位于org.springframework.cache.ehcache包中。同样,要使用它,您需要声明适当的CacheManager。下面的例子展示了如何做到这一点:这个设置在Spring IoC中引导ehcache库(通过ehcache bean),然后将其连接到专用的CacheManager实现中。注意,整个特定于ehcache的配置都是从ehcache.xml读取的。
在这里插入图片描述

5.6.3 Caffeine Cache

5.6.4 GemFire-based Cache

5.6.5 JSR-107 Cache

5.7 Plugging-in Different Back-end Caches(插入不同的后端缓存)

显然,有很多缓存产品可以用作备份存储。对于那些不支持JSR-107的,你需要提供一个CacheManager和一个Cache实现。这听起来可能比实际困难,因为,在实践中,类往往是简单的适配器,映射在存储API之上的缓存抽象框架,就像ehcache类一样。大多数CacheManager类都可以使用org.springframework.cache.support包中的类(比如AbstractCacheManager,它负责样板代码,只留下实际的映射需要完成)。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐