SpringBoot——缓存

1、基本介绍

1.1、Cache与Buffer

Buffer意为缓冲区,当请求与响应过程中,数据传输相比逻辑处理更加耗时时,可以通过缓冲区先将多个请求缓存起来,然后一起发送到服务端;服务端处理后再将多个结果通过缓冲区一同返回客户端。缓冲区本质上起到了将多个请求一同发送到服务端,从而减少数据在客户端和服务端之间传输的次数,进而减少客户端等待时间的作用。

Cache意为缓存,当一个请求从发起到得到响应比较耗时、且响应结果不会频繁变化时,可以将结果暂存在某个地方,后续一段时间内的相同请求直接将结果返回即可,减少处理时间。缓存本质上起到了暂存并复用某个请求的处理结果,从而减少处理次数,减少等待时间的作用。

1.2、SpringBoot缓存

SpringBoot提供了缓存支持,只需要在某个配置类上添加【@EnableCaching】注解,SpringBoot就会自动进行配置。在public方法上添加【@Cacheable】注解,当外部调用该方法时即可使用缓存。由于Java注解不会从接口继承,即便在接口上添加了注解,实现类依然需要添加注解才能正常使用缓存支持,因此Spring推荐只在具体类上使用注解。

SpringBoot缓存默认基于代理,只能通过代理拦截方法调用。在方法被请求时,缓存机制会检测这个方法是否以当前的参数执行过,如果执行过则返回缓存结果;如果没有执行过,则执行该方法并将结果缓存起来。使用缓存的前提是:对于同样的参数,方法总是返回同样的结果

为了使用Spring缓存,需要关注两个方面:

  • 缓存声明:确定需要添加缓存的方法以及缓存策略。
  • 缓存配置:数据存储和读取的媒介。

1.3、基于注解的声明式缓存

SpringBoot缓存提供了如下注解:

1.3.1、@Cacheable

// books为该方法对应的缓存名称
@Cacheable("books")
public Book findBook(ISBN isbn) {...}

方法的执行结果会被缓存。相同参数的方法调用直接返回缓存数据,不执行方法。缓存本质是键值对存储,方法调用需要映射为对应的键。SpringBoot使用KeyGenerator进行映射,规则如下:

  • 如果未提供参数,返回SimpleKey.EMPTY
  • 如果只提供了一个参数,返回该实例
  • 如果提供了多个参数,返回一个包含所有参数的SimpleKey

该规则有效的前提是:参数具有自然键、有效的hashCode()、equals()。如有需要,可以通过实现【org.springframework.cache.interceptor.KeyGenerator】接口进行自定义,并通过属性【keyGenerator】设置自定义bean的名称。

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

如果一个方法拥有多个参数,可通过属性【key】指定缓存的目标参数,即缓存根据目标参数生成key,也就会根据目标参数的变化进行缓存数据的存储。注:key和keyGenerator是互斥的。

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

SpringBoot缓存默认由ConcurrentMapCacheManager管理。可通过实现CacheManager接口进行自定义,并通过属性【cacheNames、cacheManager】给特定的缓存指定缓存管理器。

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") 
public Book findBook(ISBN isbn) {...}

在多线程环境下,可能存在多个线程以相同参数同步调用某个方法的情况。由于SpringBoot缓存默认不加锁,导致方法被执行多次。此时,可以使用属性【sync=true】在处理缓存时加锁,开启线程同步。

如果不希望方法一直使用缓存,可通过属性【condition】设置缓存条件,条件为true时才使用缓存。还可以通过属性【unless】在执行方法后判断是否拒绝缓存结果,如果unless表达式结果为true,则拒绝缓存结果,即结果不放入缓存。

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") 
public Book findBook(String name)

1.3.2、@CacheEvict

@CacheEvict(cacheNames="books", allEntries=true,beforeInvocation=true) 
public void loadBooks(InputStream batch)

方法执行前会根据当前参数映射成键,然后将键对应的value(即缓存数据)从缓存中移除。【allEntries=true】表示清空books缓存。【beforeInvocation=true】表示方法执行之前移除缓存,默认为false:表示方法执行之后移除缓存。

1.3.3、@CachePut

方法总是被执行,并根据该注解的配置更新缓存。

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)

: 【@CachePut】和【@Cacheable】存在不同的逻辑。应避免同时使用。

1.3.4、@Caching

用于在同一个方法上使用多个相同类型的注解。

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

1.3.5、@CacheConfig

用于类级别,设置当前类中所有使用缓存的方法的共同的配置。

// 设置统一的缓存名称为books,即类中所有方法涉及的缓存数据存放在同一个Map(默认存储媒介)中。
@CacheConfig("books") 
public class BookRepositoryImpl implements BookRepository {
    @Cacheable
    public Book findBook(ISBN isbn) {...}
}

2、默认缓存配置

在配置文件中设置

debug=true

即可查看SpringBoot启动时自动配置的类匹配情况。其中,可以看到匹配到了:【CacheAutoConfiguration、SimpleCacheConfiguration。】

【CacheAutoConfiguration】中的静态内部类【CacheConfigurationImportSelector】从【CacheConfigurations】中根据【CacheType】获取了对应的缓存配置类。得知:缓存类型与缓存配置类一一对应。

【SimpleCacheConfiguration】被匹配,说明默认缓存类型为【CacheType.SIMPLE】。并且【SimpleCacheConfiguration】提供了缓存管理器【ConcurrentMapCacheManager】,该缓存管理器内部使用【ConcurrentHashMap】存储数据。可知,SpringBoot缓存默认使用内存中的ConcurrentHashMap作为缓存媒介。

【ConcurrentMapCacheManager】默认dynamic=true,即动态模式,表示动态添加缓存名称。可在配置文件中通过spring.cache.cache-names指定缓存名称List,启动时会调用如下方法添加缓存名称,并设置dynamic=false,即静态模式,表示运行时无法添加新的缓存名称。从该方法可以看出,每个缓存名称对应一个ConcurrentHashMap实例。

public void setCacheNames(@Nullable Collection<String> cacheNames) {
    if (cacheNames != null) {
        for (String name : cacheNames) {
            this.cacheMap.put(name, createConcurrentMapCache(name));
        }
        this.dynamic = false;
    }else {
        this.dynamic = true;
    }
}

获取缓存方法如下:

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

如果是静态模式,则从map中获取缓存后直接返回。如果所获取的缓存名称没有配置,则会在默认缓存解析器SimpleCacheResolver的resolveCaches方法调用该方法后抛出
IllegalArgumentException,表示找不到指定的缓存名称。

如果是动态模式,存在缓存则直接返回。不存在则通过DCL(double check and lock)对当前缓存名称的map进行初始化,并存入cacheMap中。DCL可以确保同一个缓存名称只创建一个map。

3、使用Redis缓存

  • 引入Redis依赖
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  • 配置Redis与缓存
    spring:
      cache:
          cache-names: ["xiang","one"]
          # 缓存类型
          type: redis
          # redis属性
          redis:
            time-to-live: 10m
      # redis配置(redis为启动状态)
      redis:
          host: localhost
          port: 6379
全部评论

相关推荐

点赞 评论 收藏
转发
点赞 1 评论
分享
牛客网
牛客企业服务