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