Spring Cache架构、机制及使用


铿然架构 | 作者 / 铿然一叶 这是 铿然架构 的第 111 篇原创文章


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

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缓存,首先要引入如下两个库:

xml 复制代码
    <!-- 会间接引入依赖的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

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

ini 复制代码
	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缓存的映射关系为:

arduino 复制代码
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName());

那么要使用Caffeine缓存,则要满足的条件之一是在application.yml中配置:

yaml 复制代码
spring:
  cache:
    type: caffeine

2.2.2.3 ConditionalOnMissingBean

是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果注册相同类型的bean,就不会成功,它会保证你的bean只有一个,即你的实例只有一个,当你注册多个相同的bean时,会出现异常。

这里的设置为:

python 复制代码
@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类中定义:

java 复制代码
@ConfigurationProperties(prefix = "spring.cache")
public class CacheProperties {

所有cache参数都在该项下配置。

2.2.2.4.2 缓存参数

也在CacheProperties类中定义:

typescript 复制代码
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;
		}

	}

类的属性名就是配置参数,因此对应的配置参数如下:

yaml 复制代码
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

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(Cache.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class GenericCacheConfiguration {

相比另外两个类型的配置类,这里额外多了一个条件:

java 复制代码
@ConditionalOnBean(Cache.class)

要存在Cache接口的实现类实例才会使用,而spring默认没有提供这个实现类,因此不会使用它,如果要使用则要定制一个Cache Bean如:

CacheConfiguration.java

java 复制代码
@Configuration
public class CacheConfiguration {

    @Bean
    public Cache cache() {
        return new ConcurrentMapCache("Generic");
    }
}

2.3.2 各类缓存的加载顺序

各类缓存的加载顺序在如下类中返回:

CacheAutoConfiguration.java

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

ini 复制代码
	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则可,因为配置优先级高于代码里的顺序。

yaml 复制代码
spring:
  cache:
    type: none

2.4 扩展自定义缓存组件

如果以上spring默认内置cache和三方cache均不想使用,要自行定制,则可以实现以下两个接口:

然后配置一个Bean则可:

typescript 复制代码
@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将间接引入它:

xml 复制代码
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>

3.1.2 添加启动类注解EnableCaching

要使用Spring Cache,要在启动类加上EnableCaching注解:

java 复制代码
@SpringBootApplication
@EnableCaching
public class StartupApplication {

如果只是使用spring内置的默认cache组件,上述操作已经满足,如果要使用三方cache组件则根据需要添加starter库:

xml 复制代码
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

以及三方cache依赖库,例如:

xml 复制代码
    <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代码如下:

java 复制代码
public class SimpleKey implements Serializable {
    public static final SimpleKey EMPTY = new SimpleKey(new Object[0]);

● 如果有一个参数则该参数作为key,例如bookId作为key:

java 复制代码
@Cacheable("books")
public Book findBook(String bookId) {...}

● 如果有多个参数则多个参数的hashcode作为key,例如书名和作者作为key:

java 复制代码
@Cacheable("books")
public Book findBook(String bookName, String author) {...}

3.2.3 key属性

可以通过key属性指定key,遵循SEL语法。 例子:

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

3.2.4 keyGenerator属性

keyGenerator属性指定可以生成器,该生成器需要实现KeyGenerator接口,默认的生成器为SimpleKeyGenerator:

java 复制代码
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

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

java 复制代码
@Configuration
public class CacheConfiguration {

    // 这里的方法名为MyKeyGenerator的名称
    @Bean
    public MyKeyGenerator myKeyGenerator() {
        return new MyKeyGenerator();
    }
}

3.2.4.3 指定使用定制的keyGenerator

Cacheable注解的keyGenerator属性指定上面@Bean注解的方法名

java 复制代码
    @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

满足条件才缓存数据,例如:

ini 复制代码
    @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做查询,则不会放入缓存,每次都会调用此方法:

ini 复制代码
bookId: 103
Book{id='103', name='微服务架构模式', author='pide'}
bookId: 103
Book{id='103', name='微服务架构模式', author='pide'}

如果bookId为102,则只会调用一次,后续都从缓存获取数据:

ini 复制代码
bookId: 102
Book{id='102', name='重构', author='马丁'}

3.2.8 unless

满足条件则不放入缓存,例如:

ini 复制代码
    @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值不一样则不会加锁),则该方法会加锁,只有第一个线程会进入方法,得到结果后放入缓存,后面的线程将等待第一个线程调用完后直接从缓存获取数据,不会再重复调用该方法。

如下代码:

ini 复制代码
    @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相同,后一个线程是从缓存获取数据:

ini 复制代码
bookId: 101
Book{id='101', name='Java设计模式', author='keven'}

如果两个线程并发传入bookId分别为102和103,则两个都会打印,因为key不同,不会加锁:

ini 复制代码
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分别放入不同缓存,则注解如下:

java 复制代码
    @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基础(五)函数式接口-复用,解耦之利刃

相关推荐
c++之路19 分钟前
C++20概述
java·开发语言·c++20
Championship.23.2423 分钟前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试
橘子海全栈攻城狮38 分钟前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序
逻辑驱动的ken44 分钟前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法
冷雨夜中漫步1 小时前
Claude Code源码分析——Claude Code Agent Loop 详细设计文档
java·开发语言·人工智能·ai
直奔標竿1 小时前
Java开发者AI转型第二十六课!Spring AI 个人知识库实战(五)——联网搜索增强实战
java·开发语言·人工智能·spring boot·后端·spring
one_love_zfl2 小时前
java面试-微服务组件篇
java·微服务·面试
一只大袋鼠2 小时前
Java进阶:CGLIB动态代理解析
java·开发语言
环流_2 小时前
HTTP 协议的基本格式
java·网络协议·http
爱滑雪的码农2 小时前
Java基础十三:Java中的继承、重写(Override)与重载(Overload)详解
java·开发语言