springboot缓存如何使用
一、 JSR107
Java Caching定义了5个核心接口
CachingProvider
定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可
以在运行期访问多个CachingProvider。
CacheManager
定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache
存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
Cache
一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个
CacheManager所拥有。
Entry
一个存储在Cache中的key-value对。
Expiry
每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

二、 Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager接口来统一不同的缓存技术;
并支持使用JCache(JSR-107)注解简化我们开发;
Cache接口有以下功能:
- 为缓存的组件规范定义,包含缓存的各种操作集合;
Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,ConcurrentMapCache等;

三、 重要缓存注解及概念
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 更新缓存 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
1 . @Cacheable/@CachePut/@CacheEvict 主要的参数
value
缓存名称,字符串/字符数组形式;
如@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}
key
缓存的key,需要按照SpEL表达式编写,如果不指定则按照方法所有参数进行组合;
如@Cacheable(value=”testcache”,key=”#userName”)
keyGenerator
key的生成器;可以自己指定key的生成器的组件id
注意:key/keyGenerator:二选一使用;
condition
缓存条件,使用SpEL编写,在调用方法之前之后都能判断;
如@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
unless(@CachePut、@Cacheable)
用于否决缓存的条件,只在方法执行之后判断;
如@Cacheable(value=”testcache”,unless=”#result ==null”)
beforeInvocation(@CacheEvict)
是否在执行前清空缓存,默认为false,false情况下方法执行异常则不会清空;
如@CachEvict(value=”testcache”,beforeInvocation=true)
allEntries(@CacheEvict)
是否清空所有缓存内容,默认为false;
如@CachEvict(value=”testcache”,allEntries=true)
2 . 缓存可用的SpEL表达式
root
表示根对象,不可省略
被调用方法名 methodName
如 #root.methodName
被调用方法 method
如 #root.method.name
目标对象 target
如 #root.target
被调用的目标对象类 targetClass
如 #root.targetClass
被调用的方法的参数列表 args
如 #root.args[0]
方法调用使用的缓存列表 caches
如 #root.caches[0].name
参数名
方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引;
如 #iban 、 #a0 、 #p0
返回值
方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’ , @CachePut、@CacheEvict’的表达式beforeInvocation=false )
如 #result
四、 缓存使用
1. 基本使用步骤
- 引入spring-boot-starter-cache模块
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
@EnableCaching开启缓存
在主配置类上标注
使用缓存注解
如@Cacheable、@CachePut
切换为其他缓存
2. 搭建实验环境
导入数据库文件 创建出department和employee表
-- ----------------------------
-- Table structure for department
-- ----------------------------
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`departmentName` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`lastName` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`gender` int(2) DEFAULT NULL,
`d_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建javaBean封装数据
整合MyBatis操作数据库
配置数据源信息
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 开启驼峰命名法(否则部分字段封装不了)
mybatis.configuration.map-underscore-to-camel-case=true
#打印sql
logging.level.cn.edu.ustc.springboot.mapper=debug
debug=true
使用注解版的MyBatis;
@MapperScan指定需要扫描的mapper接口所在的包
主配置类开启@EnableCaching
3. 快速体验缓存
@Cacheable、@CachePut、@CacheEvict的使用
@Service
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
@Cacheable(value={"emp"},
key = "#id+#root.methodName+#root.caches[0].name",
condition = "#a0>1",
unless = "#p0==2"
)
public Employee getEmpById(Integer id) {
System.out.println("查询员工:"+id);
return employeeMapper.getEmpById(id);
}
@CachePut(value = {"emp"},key = "#employee.id" )
public Employee updateEmp(Employee employee) {
System.out.println("更新员工"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
@CacheEvict(value = {"emp"},allEntries = true,beforeInvocation = true)
public Integer delEmp(Integer id){
int i=1/0;
System.out.println("删除员工:"+id);
employeeMapper.delEmp(id);
return id;
}
}
自定义KeyGenerator
使用时在注解属性内指定KeyGenerator=“myKeyGenerator”
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator myKeyGenerator() {
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+target+"]";
}
};
}
}
@CacheConfig
标注在类上,用于抽取@Cacheable的公共属性
由于一个类中可能会使用多次@Cacheable等注解,所以各项属性可以抽取到@CacheConfig
@Caching
组合使用@Cacheable、@CachePut、@CacheEvict
@Caching(
cacheable = {
@Cacheable(/*value="emp",*/key = "#lastName")
},
put = {
@CachePut(/*value="emp",*/key = "#result.id"),
@CachePut(/*value="emp",*/key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
4. 工作原理
缓存的自动配置类CacheAutoConfiguration向容器中导入了CacheConfigurationImportSelector,此类的selectImports()方法添加了许多配置类,其中SimpleCacheConfiguration默认生效
GenericCacheConfiguration
JCacheCacheConfiguration
EhCacheCacheConfiguration
HazelcastCacheConfiguration
InfinispanCacheConfiguration
CouchbaseCacheConfiguration
RedisCacheConfiguration
CaffeineCacheConfiguration
GuavaCacheConfiguration
SimpleCacheConfiguration【默认】
NoOpCacheConfiguration
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
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;
}
}
}
SimpleCacheConfiguration向容器中导入了ConcurrentMapCacheManager
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
//向容器中导入ConcurrentMapCacheManager
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}
ConcurrentMapCacheManager使用ConcurrentMap以k-v的方式存储缓存缓存,下面以@Cacheable的运行流程为例说明ConcurrentMapCacheManager的作用。
==@Cacheable的运行流程==
方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建,并以cacheNames-cache对放入ConcurrentMap。
去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
SimpleKeyGenerator生成key的默认策略;
如果没有参数;key=new SimpleKey();
如果有一个参数:key=参数的值
如果有多个参数:key=new SimpleKey(params);
没有查到缓存就调用目标方法;
将目标方法返回的结果,放进缓存中
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
核心:
1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
源码分析
默认使用ConcurrentMapCacheManager管理缓存,该类使用ConcurrentMap保存缓存,获取缓存如果没有Cache组件会自动创建,并以cacheNames-cache对放入ConcurrentMap。
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>();
private boolean 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;
}
}
在@Cacheable标注方法执行前执行CacheAspectSupport的execute()方法,在该方法中会以一定的规则生成key,并尝试在缓存中通过该key获取值,若通过key获取到值则直接返回,不用执行@Cacheable标注方法,否则执行该方法获得返回值。
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
//在执行@Cacheable标注的方法前执行此方法
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
}
catch (Cache.ValueRetrievalException ex) {
throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
}
}
else {
return invokeOperation(invoker);
}
}
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// 见findCachedItem方法
//此方法通过一定规则生成的key找cache,若没找到则返回null
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
List<CachePutRequest> cachePutRequests = new LinkedList<>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
if (cacheHit != null && !hasCachePut(contexts)) {
// 如果通过该key找到缓存,且无@cacheput,则直接返回cacheValue
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// 若通过该key未找到缓存,则执行@cacheable标注方法
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
@Nullable
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
//通过一定规则生成key值(生成规则见generateKey方法)
Object key = generateKey(context, result);
//通过生成的key寻找缓存
Cache.ValueWrapper cached = findInCaches(context, key);
if (cached != null) {
return cached;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
}
}
}
}
return null;
}
//key的生成策略
@Nullable
protected Object generateKey(@Nullable Object result) {
//如果@Cacheable设置了属性key,则根据设置值生成key
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
}
//否则使用keyGenerator生成key,默认keyGenerator为SimpleKeyGenerator
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
默认情况下使用SimpleKeyGenerator生成key
public class SimpleKeyGenerator implements KeyGenerator {
//SimpleKeyGenerator的生成规则
public static Object generateKey(Object... params) {
//若无参,则返回空key
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
//1个参数,则直接返回该参数
return param;
}
}
//多个参数返回数组
return new SimpleKey(params);
}
}
默认的缓存类ConcurrentMapCache,使用ConcurrentMap存储k-v
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final String name;
//存储key-cacheValue
private final ConcurrentMap<Object, Object> store;
//通过key查找cacheValue
protected Object lookup(Object key) {
return this.store.get(key);
}
//方法调用完后将结果存入缓存中
public void put(Object key, @Nullable Object value) {
this.store.put(key, toStoreValue(value));
}
}
版权声明:
作者:Miigua
链接:https://www.miigua.com/article/334.html
来源:米瓜的博客
文章版权归作者所有,未经允许请勿转载。
Mariounela
Frenchmxjf