1. 介绍
Spring Cache的重点并不在Cache实现上,而是提供了一个Cache管理框架,使得不用修改业务代码就可以很方便的替换cache实现(可替换性),这样在项目中替换不同的cache方案就变得很容易。
本文将介绍spring cache是如何做到可替换,及其背后的原理,以及如何使用spring cache。
2. 缓存组件及其原理
2.1 概述
Spring Cache相关的库和核心类如下(这里以caffeine为例说明核心原理):
库 | 描述 |
---|---|
spring-context | 提供了spring cache的接口、cache默认实现,以及cache相关的注解,是spring cache的核心库,同时是一个最小实现,有了这个库就可以使用spring cache了。 |
spring-context-support | 三方缓存的支持库,起到适配器作用,一方面实现了spring cache的接口,另一方面封装了三方cache的具体实现类,通过接口来访问三方cache。 |
spring-boot-starter-cache | 使用三方cache的启动库,没有实际代码,只有依赖关系,作用是触发加载三方cache库从而能使用它。 |
spring-boot-autoconfigure | 通过各种三方cache的XXXConfiguration配置类预定义cache加载条件,满足对应条件就可以加载该cache,同时读取对应配置参数。 |
caffeine | 三方caffeine cache库 |
整个架构还是比较清晰的,包括:
序号 | 类型 |
---|---|
核心部分 | spring-context |
使用约束/条件 | spring-boot-autoconfigure |
适配层 | spring-context-support |
可拔插 | spring-boot-starter-cache |
三方库 | com.github.ben-manes.caffeine/caffeine |
这里有个问题:
对于“可拔插”这个库是否可以去掉,直接引入spring-context-support库?
理论上应该是可以,但通过spring-boot-starter-XXXX启用某项能力应该是spring一个约定惯例,都统一通过此方式来引入外部能力。
2.2 控制使用哪个缓存组件
2.2.1 基础介绍
上节提到XXXConfiguratio预定义了三方cache加载条件,来控制满足什么条件才加载三方cache,系统内置的XXXConfiguratio如下:
配置类 | 描述 |
---|---|
GenericCacheConfiguration | 内置能力,spring仅提供接口,需要定制返回Cache实例才能使用,提供了一个轻量扩展能力。 |
EhCacheCacheConfiguration | EhCache缓存 |
HazelcastCacheConfiguration | Hazelcast缓存 |
InfinispanCacheConfiguration | Infinispan缓存 |
JCacheCacheConfiguration | JCacheCache缓存 |
CouchbaseCacheConfiguration | Couchbase缓存 |
RedisCacheConfiguration | Redis缓存 |
CaffeineCacheConfiguration | Caffeine缓存 |
Cache2kCacheConfiguration | Cache2k缓存 |
SimpleCacheConfiguration | spring内置缓存,底层使用ConcurrentHashMap。 |
NoOpCacheConfiguration | spring内置,不缓存数据。 |
综合来看,这些默认实现考虑得比较全面,分成四大类:
● 提供扩展接口,允许定制实现Cache接口(轻量定制)。
● 使用第三方的缓存组件。
● 开箱即用的默认cache实现。
● 不做缓存操作的内置实现。
2.2.2 caffeine cache举例
下面以caffeine cache为例说明如何控制三方cache库使用,caffeine的配置类如下:
CaffeineCacheConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class })
@ConditionalOnMissingBean(CacheManager.class)
@Conditional({ CacheCondition.class })
class CaffeineCacheConfiguration {
@Bean
CaffeineCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers,
ObjectProvider<Caffeine<Object, Object>> caffeine, ObjectProvider<CaffeineSpec> caffeineSpec,
ObjectProvider<CacheLoader<Object, Object>> cacheLoader) {
CaffeineCacheManager cacheManager = createCacheManager(cacheProperties, caffeine, caffeineSpec, cacheLoader);
List<String> cacheNames = cacheProperties.getCacheNames();
if (!CollectionUtils.isEmpty(cacheNames)) {
cacheManager.setCacheNames(cacheNames);
}
return customizers.customize(cacheManager);
}
2.2.2.1 ConditionalOnClass
ConditionalOnClass表示仅有你指定的类在类路径上时才匹配 @Conditional注解。
这里指定的类和所在库如下:
类 | 所在库 |
---|---|
Caffeine | caffeine |
CaffeineCacheManager | spring-context-support |
从前面的架构图可以看出,spring-context-support仅被spring-boot-starter-cache依赖,所以要想使用Caffeine缓存,首先要引入如下两个库:
<!-- 会间接引入依赖的spring-context-support库 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
2.2.2.2 Conditional
满足指定bean的条件才加载当前类,这里指定的类如下:
CacheCondition.java
class CacheCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sourceClass = "";
if (metadata instanceof ClassMetadata) {
sourceClass = ((ClassMetadata) metadata).getClassName();
}
ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", sourceClass);
Environment environment = context.getEnvironment();
try {
BindResult<CacheType> specified = Binder.get(environment).bind("spring.cache.type", CacheType.class);
if (!specified.isBound()) {
return ConditionOutcome.match(message.because("automatic cache type"));
}
CacheType required = CacheConfigurations.getType(((AnnotationMetadata) metadata).getClassName());
if (specified.get() == required) {
return ConditionOutcome.match(message.because(specified.get() + " cache type"));
}
}
catch (BindException ex) {
}
return ConditionOutcome.noMatch(message.because("unknown cache type"));
}
}
此类的核心逻辑是参考application.yml配置文件中的spring.cache.type属性配置,如果该属性没有配置,则XXXConfiguration类上的其他条件满足则可,否则还需要检查属性值是否和XXXConfiguration类映射的值一致。这个映射值的关系定义在:
CacheConfigurations.java
static {
Map<CacheType, String> mappings = new EnumMap<>(CacheType.class);
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName());
mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class.getName());
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName());
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName());
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName());
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName());
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName());
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName());
mappings.put(CacheType.CACHE2K, Cache2kCacheConfiguration.class.getName());
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName());
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName());
MAPPINGS = Collections.unmodifiableMap(mappings);
}
例如Caffeine缓存的映射关系为:
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName());
那么要使用Caffeine缓存,则要满足的条件之一是在application.yml中配置:
spring:
cache:
type: caffeine
2.2.2.3 ConditionalOnMissingBean
是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果注册相同类型的bean,就不会成功,它会保证你的bean只有一个,即你的实例只有一个,当你注册多个相同的bean时,会出现异常。
这里的设置为:
@ConditionalOnMissingBean(CacheManager.class)
意思是不存在CacheManager的实例才会加载此类。
2.2.2.4 缓存运行需要的参数
当使用三方缓存时需要配置对应的参数,参数获取的主要类如下:
类 | 描述 |
---|---|
CacheProperties | 缓存相关的配置参数都从这个类获取,除了有type参数指定是哪类cache,还聚合了所有三方cache的配置参数类。 |
Spring-Caffeine | Caffeine对应的配置参数类,只有一个字段spec来配置参数,多个参数之间用逗号(,)分隔,参数键值用等号(=)分隔。 |
CaffeineSpec | Caffeine的规格定义类,可以解析配置参数,并且可根据它生成三方Caffeine库的Caffeine类。 |
2.2.2.4.1 参数配置项
在CacheProperties类中定义:
@ConfigurationProperties(prefix = "spring.cache")
public class CacheProperties {
所有cache参数都在该项下配置。
2.2.2.4.2 缓存参数
也在CacheProperties类中定义:
public class CacheProperties {
private CacheType type;
private final Caffeine caffeine = new Caffeine();
public static class Caffeine {
private String spec;
public String getSpec() {
return this.spec;
}
public void setSpec(String spec) {
this.spec = spec;
}
}
类的属性名就是配置参数,因此对应的配置参数如下:
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=1000,expireAfterAccess=3600s
参数 | 对应类属性 |
---|---|
type | CacheProperties.type |
caffeine | CacheProperties.caffeine |
spec | CacheProperties.caffeine.spec |
caffeine支持哪些参数可以在CaffeineSpec类查看,对应属性就是可配置参数。
2.3 默认缓存实现
前面提到,spring提供了默认缓存实现,在spring-context库中,该库提供了如下几个缓存实现:
配置类 | 管理类 | 对应缓存实现类型 |
---|---|---|
GenericCacheConfiguration | SimpleCacheManager | CacheType.GENERIC |
SimpleCacheConfiguration | ConcurrentMapCacheManager | CacheType.SIMPLE |
NoOpCacheConfiguration | NoOpCacheManager | CacheType.NONE |
spring默认使用的是SimpleCacheConfiguration,那么为啥默认使用它?
2.3.1 内置GENERIC缓存加载
首先看CacheType.GENERIC类型的XXXCacheConfiguration类:
GenericCacheConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(Cache.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class GenericCacheConfiguration {
相比另外两个类型的配置类,这里额外多了一个条件:
@ConditionalOnBean(Cache.class)
要存在Cache接口的实现类实例才会使用,而spring默认没有提供这个实现类,因此不会使用它,如果要使用则要定制一个Cache Bean如:
CacheConfiguration.java
@Configuration
public class CacheConfiguration {
@Bean
public Cache cache() {
return new ConcurrentMapCache("Generic");
}
}
2.3.2 各类缓存的加载顺序
各类缓存的加载顺序在如下类中返回:
CacheAutoConfiguration.java
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for(int i = 0; i < types.length; ++i) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
// 这里返回数组成员的顺序就是加载顺序
return imports;
}
返回的imports是准备加载的类(另外要满足了预定条件才会实际加载),顺序如下(从上往下):
这个顺序实际就是代码里的顺序:
CacheConfigurations.java
static {
Map<CacheType, String> mappings = new EnumMap<>(CacheType.class);
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName());
mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class.getName());
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName());
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName());
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName());
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName());
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName());
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName());
mappings.put(CacheType.CACHE2K, Cache2kCacheConfiguration.class.getName());
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName());
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName());
MAPPINGS = Collections.unmodifiableMap(mappings);
}
可以看到三个默认实现类的顺序依次是:
GenericCacheConfiguration、 SimpleCacheConfiguration、 NoOpCacheConfiguration
前面已经提到GenericCacheConfiguration加载需要自定义Cache的实现类,由于spring默认没有定义它,所以按照顺序接下来加载的就是SimpleCacheConfiguration类。
注:其他三方cache也都有各自的条件,默认情况下也必然不满足,自然不会使用。
最后,如果要改变SimpleCacheConfiguration、NoOpCacheConfiguration的默认顺序,只需要在application.yml中指定使用NoOpCache则可,因为配置优先级高于代码里的顺序。
spring:
cache:
type: none
2.4 扩展自定义缓存组件
如果以上spring默认内置cache和三方cache均不想使用,要自行定制,则可以实现以下两个接口:
然后配置一个Bean则可:
@Configuration
public class CacheConfiguration {
@Bean
public CacheManager cacheManager() {
return new MyCacheManager();
}
}
bean的名字为默认的“cacheManager”。
注:自定义的CacheManager bean优先级最高,即使修改spring.cache.type为其他缓存类型也不会生效。
3. 使用
3.1 使用前提
3.1.1 添加maven依赖
Spring Cache代码在spring-context模块中,引入spring-boot-autoconfigure将间接引入它:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
3.1.2 添加启动类注解EnableCaching
要使用Spring Cache,要在启动类加上EnableCaching注解:
@SpringBootApplication
@EnableCaching
public class StartupApplication {
如果只是使用spring内置的默认cache组件,上述操作已经满足,如果要使用三方cache组件则根据需要添加starter库:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
以及三方cache依赖库,例如:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
3.2 Cacheable注解
Cacheable的作用是将数据放入缓存,如果数据已存在缓存中则不会刷新,因此一般用在读操作。
3.2.1 属性概述
属性 | 描述 |
---|---|
cacheNames | 指定缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。 |
key | 缓存数据时的key的值,默认是使用方法所有入参的值,可以使用SpEL表达式表示key的值。 |
keyGenerator | 缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。 |
cacheManager | 指定缓存管理器的bean名称,如果不指定则使用默认的cacheManager。 |
cacheResolver | 和cacheManager作用一样,使用时二选一。 |
condition | 指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。 |
unless | 否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。 |
sync | 是否同步操作,如果配置为true,多线程并发时只有一个线程能操作,其他线程将等待 |
3.2.2 key的默认规则
● 如果方法没有参数,则key为SimpleKey.EMPTY。这在多数场景下都不太合适,会导致key重复,SimpleKey.EMPTY代码如下:
public class SimpleKey implements Serializable {
public static final SimpleKey EMPTY = new SimpleKey(new Object[0]);
● 如果有一个参数则该参数作为key,例如bookId作为key:
@Cacheable("books")
public Book findBook(String bookId) {...}
● 如果有多个参数则多个参数的hashcode作为key,例如书名和作者作为key:
@Cacheable("books")
public Book findBook(String bookName, String author) {...}
3.2.3 key属性
可以通过key属性指定key,遵循SEL语法。 例子:
@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)
3.2.4 keyGenerator属性
keyGenerator属性指定可以生成器,该生成器需要实现KeyGenerator接口,默认的生成器为SimpleKeyGenerator:
package org.springframework.cache.interceptor;
import java.lang.reflect.Method;
public class SimpleKeyGenerator implements KeyGenerator {
public SimpleKeyGenerator() {
}
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
} else {
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
}
如果要自定义KeyGenerator并使用则按照以下步骤实现.
3.2.4.1 定义实现类
一个例子:
MyKeyGenerator.java
public class MyKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(target.getClass().getSimpleName());
for (Object param: params) {
stringBuilder.append(param);
}
return stringBuilder.toString();
}
}
3.2.4.2 配置这个bean
CacheConfiguration.java
@Configuration
public class CacheConfiguration {
// 这里的方法名为MyKeyGenerator的名称
@Bean
public MyKeyGenerator myKeyGenerator() {
return new MyKeyGenerator();
}
}
3.2.4.3 指定使用定制的keyGenerator
Cacheable注解的keyGenerator属性指定上面@Bean注解的方法名
@Cacheable(value = "books", keyGenerator = "myKeyGenerator")
public Book findBookForKeyGenerator(String bookId) {
Book book = MockBooks.findBook(bookId);
return book;
}
3.2.5 cacheManager
指定使用的cacheManager bean名称,spring内置的名称都是"cacheManager",如果要定制实现CacheManager并指定bean名称,在一般场景下看起来有点多余,因为当同时存在多个CacheManager实例时会报错,而如果只能有1个实例,即便不指定此属性,也会用该实例。
或许有其他特殊场景可以使用,暂未发现该场景。
3.2.6 cacheResolver
指定使用的cacheResolver bean名称。
3.2.7 condition
满足条件才缓存数据,例如:
@Cacheable(value = "books", condition = "#bookId.equals(\"102\")")
public Book findBookForCondition(String bookId) {
System.out.println("bookId: " + bookId);
Book book = MockBooks.findBook(bookId);
System.out.println(book);
return book;
}
当输入bookId为103做查询,则不会放入缓存,每次都会调用此方法:
bookId: 103
Book{id='103', name='微服务架构模式', author='pide'}
bookId: 103
Book{id='103', name='微服务架构模式', author='pide'}
如果bookId为102,则只会调用一次,后续都从缓存获取数据:
bookId: 102
Book{id='102', name='重构', author='马丁'}
3.2.8 unless
满足条件则不放入缓存,例如:
@Cacheable(value = "books", unless = "#bookId.equals(\"102\")")
public Book findBookForUnless(String bookId) {
System.out.println("bookId: " + bookId);
Book book = MockBooks.findBook(bookId);
System.out.println(book);
return book;
}
这个例子跟上面condition是相反的,如果bookId为102则不放入缓存,其他都放入缓存。
3.2.9 sync
当配置为true,同一个方法被多个线程并发调用时,如果key的值一样(key值不一样则不会加锁),则该方法会加锁,只有第一个线程会进入方法,得到结果后放入缓存,后面的线程将等待第一个线程调用完后直接从缓存获取数据,不会再重复调用该方法。
如下代码:
@Cacheable(value = "books", sync = true)
public Book findBook(String bookId) {
System.out.println("bookId: " + bookId);
pause(50);
Book book = MockBooks.findBook(bookId);
System.out.println(book);
return book;
}
如果两个线程并发传入bookId为101,日志打印一次101,因为key相同,后一个线程是从缓存获取数据:
bookId: 101
Book{id='101', name='Java设计模式', author='keven'}
如果两个线程并发传入bookId分别为102和103,则两个都会打印,因为key不同,不会加锁:
bookId: 103
bookId: 102
Book{id='103', name='微服务架构模式', author='pide'}
Book{id='102', name='重构', author='马丁'}
3.3 CacheConfig注解
CacheConfig是类级别的注解,类上配置后方法上可以继承。
3.3.1 属性概述
属性 | 描述 |
---|---|
cacheNames | 指定缓存的名字,缓存使用CacheManager管理多个缓存 |
keyGenerator | 缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。 |
cacheManager | 指定缓存管理器的bean名称,如果不指定则使用默认的cacheManager。 |
cacheResolver | 和cacheManager作用一样,使用时二选一。 |
属性含义同Cacheable注解同名属性。
3.4 CachePut注解
CachePut注解做作用是更新缓存,和Cacheable的区别是每次操作都会更新,不管数据是否已经在缓存中,一般用在数据更新操作。
3.4.1 属性概述
属性与用法和Cacheable相同,相比Cacheable少了sync属性。
属性 | 描述 |
---|---|
cacheNames | 指定缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。 |
key | 缓存数据时的key的值,默认是使用方法所有入参的值,可以使用SpEL表达式表示key的值。 |
keyGenerator | 缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。 |
cacheManager | 指定缓存管理器的bean名称,如果不指定则使用默认的cacheManager。 |
cacheResolver | 和cacheManager作用一样,使用时二选一。 |
condition | 指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。 |
unless | 否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。 |
3.5 CacheEvict注解
CacheEvict注解是Cachable注解的反向操作,从指定缓存中移除一个值。大多数缓存框架都提供了缓存数据的有效期,使用该注解可以显式地从缓存中删除失效的缓存数据,该注解通常用于更新或者删除操作。
3.5.1 属性概述
属性 | 描述 |
---|---|
cacheNames | 指定缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。 |
key | 缓存数据时的key的值,默认是使用方法所有入参的值,可以使用SpEL表达式表示key的值。 |
keyGenerator | 缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。 |
cacheManager | 指定缓存管理器的bean名称,如果不指定则使用默认的cacheManager。 |
cacheResolver | 和cacheManager作用一样,使用时二选一。 |
condition | 指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。 |
allEntries | 是否需要清除缓存中的所有元素。默认值为false,表示不需要。当指定allEntries为true时,Spring Cache将忽略指定的key,清除缓存中的所有内容。 |
beforeInvocation | 清除操作默认是在对应方法执行成功后触发(beforeInvocation = false),如抛出异常未能成功返回则不会触发清除操作。使用beforeInvocation属性可以改变触发清除操作的时间,当指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。 |
3.6 Caching注解
Caching有点像一个注解集合,可以同时分别指定多个Cacheable、CachePut、CacheEvict注解,其中一个应用场景为方法参数是父类,但是实际可能会传入多个不同子类,那么就要用多个注解分别判断是哪个子类从而放入到不同的缓存中。
3.6.1 属性概述
属性 | 描述 |
---|---|
cacheable | Cacheable注解数组,每个Cacheable注解可以限定不同条件 |
put | CachePut注解数组,每个CachePut注解可以限定不同条件 |
evict | CacheEvict注解数组,每个CacheEvict注解可以限定不同条件 |
3.6.2 例子
例如有如下类结构:
要将EnterpriseCustomer和PersonalCustomer分别放入不同缓存,则注解如下:
@Caching(cacheable = {
@Cacheable(value = "pCustomer", condition = "#customer instanceof T(" +
"com.example.domain.PersonalCustomer)"),
@Cacheable(value = "eCustomer", condition = "#customer instanceof T(" +
"com.example.domain.EnterpriseCustomer)")
})
public void addCustomer(Customer customer) {
// dothing...
}
4. 总结
通过本文,我们学习了以下知识:
● Spring cache的架构
● 如何适配三方cache库的机制
● 内置cache的加载顺序,这个曾经给不少人造成困惑,因为没有类似实现Order接口决定顺序的机制,单纯看代码很难看出来。
● 如何定制扩展自己的cache组件
● Spring cache如何使用
至此,对于如何使用spring cache已经可以做到心中有数,而继续深入下去还有一些问题可以思考,例如:
● 如何根据不同场景使用不同的三方cache库?从上面的描述看,系统中只能存在一个CacheManager实例,当有多个就会报错。
● 如何控制每个缓存的失效时间?
这些都是在实际业务中可能会碰到的问题,就留给大家去发挥动手能力了!
其他阅读:
萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃