likes
comments
collection
share

Spring Cache架构、机制及使用

作者站长头像
站长
· 阅读数 6

Spring Cache架构、机制及使用

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

1. 介绍

Spring Cache的重点并不在Cache实现上,而是提供了一个Cache管理框架,使得不用修改业务代码就可以很方便的替换cache实现(可替换性),这样在项目中替换不同的cache方案就变得很容易。

本文将介绍spring cache是如何做到可替换,及其背后的原理,以及如何使用spring cache。

2. 缓存组件及其原理

2.1 概述

Spring Cache相关的库和核心类如下(这里以caffeine为例说明核心原理):

Spring Cache架构、机制及使用

描述
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实例才能使用,提供了一个轻量扩展能力。
EhCacheCacheConfigurationEhCache缓存
HazelcastCacheConfigurationHazelcast缓存
InfinispanCacheConfigurationInfinispan缓存
JCacheCacheConfigurationJCacheCache缓存
CouchbaseCacheConfigurationCouchbase缓存
RedisCacheConfigurationRedis缓存
CaffeineCacheConfigurationCaffeine缓存
Cache2kCacheConfigurationCache2k缓存
SimpleCacheConfigurationspring内置缓存,底层使用ConcurrentHashMap。
NoOpCacheConfigurationspring内置,不缓存数据。

综合来看,这些默认实现考虑得比较全面,分成四大类:

● 提供扩展接口,允许定制实现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注解。

这里指定的类和所在库如下:

所在库
Caffeinecaffeine
CaffeineCacheManagerspring-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 缓存运行需要的参数

当使用三方缓存时需要配置对应的参数,参数获取的主要类如下:

Spring Cache架构、机制及使用

描述
CacheProperties缓存相关的配置参数都从这个类获取,除了有type参数指定是哪类cache,还聚合了所有三方cache的配置参数类。
Spring-CaffeineCaffeine对应的配置参数类,只有一个字段spec来配置参数,多个参数之间用逗号(,)分隔,参数键值用等号(=)分隔。
CaffeineSpecCaffeine的规格定义类,可以解析配置参数,并且可根据它生成三方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
参数对应类属性
typeCacheProperties.type
caffeineCacheProperties.caffeine
specCacheProperties.caffeine.spec

caffeine支持哪些参数可以在CaffeineSpec类查看,对应属性就是可配置参数。

2.3 默认缓存实现

前面提到,spring提供了默认缓存实现,在spring-context库中,该库提供了如下几个缓存实现:

Spring Cache架构、机制及使用

配置类管理类对应缓存实现类型
GenericCacheConfigurationSimpleCacheManagerCacheType.GENERIC
SimpleCacheConfigurationConcurrentMapCacheManagerCacheType.SIMPLE
NoOpCacheConfigurationNoOpCacheManagerCacheType.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是准备加载的类(另外要满足了预定条件才会实际加载),顺序如下(从上往下):

Spring Cache架构、机制及使用

这个顺序实际就是代码里的顺序:

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均不想使用,要自行定制,则可以实现以下两个接口:

Spring 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 属性概述

属性描述
cacheableCacheable注解数组,每个Cacheable注解可以限定不同条件
putCachePut注解数组,每个CachePut注解可以限定不同条件
evictCacheEvict注解数组,每个CacheEvict注解可以限定不同条件

3.6.2 例子

例如有如下类结构:

Spring Cache架构、机制及使用

要将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实例,当有多个就会报错。

● 如何控制每个缓存的失效时间?

这些都是在实际业务中可能会碰到的问题,就留给大家去发挥动手能力了!


其他阅读: