SpringBoot 缓存介绍
- spring 定义了 org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口来统一不同的缓存技术;
- CacheManager: 缓存管理器, 管理各种缓存(Cache)组件;如: RedisCache, EhCacheCache...等.
- 每次调用需要缓存功能的方法时,spring 会检查目标方法是否已经被调用过. 如果被调用过, 会直接从缓存中获取数据, 如果没有会访问数据库并将结果缓存;
SprongBoot 缓存使用
- 创建 springboot 项目,并导入 cache, mysql, mybatis, web 模块
2. main 类中开启基于注解的缓存
@SpringBootApplication
@MapperScan("com.yangyun.cache.mapper") // 扫描mapper
@EnableCaching //开启基于注解的缓存
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
3. 缓存注解的使用
- @Cacheable 主要针对方法配置
- @CachePut 既调用方法, 也会更新缓存中的数据
- @CacheEvict 清除缓存
- @Caching 组合缓存
package com.yangyun.cache.service;
import com.yangyun.cache.bean.Employee;
import com.yangyun.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
/**
* @author yangyun
* @create 2018-12-18-10:38
*/
@CacheConfig(cacheNames = "emp") // 共享属性配置
@Service
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
/**
* 将方法的结果进行缓存, 以后再要相同的数据, 直接从缓存中获取
* CacheManager 管理多个 Cache(key,value) 组件, 对缓存的真正 CRUD 操作在 Cache 组件中, 没一个缓存组件有自己唯一的名字
* 几个属性
* cacheNames/value: 指定缓存的名字
* key: 缓存数据使用key; 默认使用方法参数 id=1 key=1
*
* key/keyGenerator 二选一使用, key 指定具体的值可以使用 spEL 表达式, keyGenerator 自定义的方式
* condition 条件成立才缓存 condition = "#a0>1" 第一个参数的值 > 1 才进行缓存
* unless 条件为 true 不缓存 unless = "#a0 == 2" 如果第一个参数的值是 2, 就不缓存
* sync 是否使用异步缓存, 默认为 false, 如果使用 sync 那么 unless 就没有效果了
* @param id
* @return
*/
@Cacheable(
value = {"emp"},
key = "#id"
)
public Employee getEmp(Integer id){
return employeeMapper.getEmpById(id);
}
/**
* @CachePut 既调用方法, 又更新缓存数据, 修改数据库的某个数据, 同事更新缓存
* 运行时机:
* 1. 先调用目标方法
* 2. 将目标方法的结果缓存
* 注意: 更新和查询缓存的 key 要一致, 在更新的时候才能保证缓存中的数据被更新, cachePut 中 value 属性值为单个
* @param employee
* @return
*/
@CachePut(value = {"emp"}, key = "#employee.id") // #result.id
public Employee updateEmp(Employee employee) {
employeeMapper.updateEmp(employee);
return employee;
}
/**
* @CacheEvict 清除缓存
* key: 指定要清除的数据
* allEntries 是否清除当前缓存所有数据默认 false
* beforeInvocation 是否在方法前清除缓存默认 false
* @param id
*/
@CacheEvict(value = {"emp"}, key = "#id")
public void deleteEmployee (Integer id){
System.out.println("remove employee " + employeeMapper.getEmpById(id));
// employeeMapper.deleteEmpById(id);
}
/**
* @Caching 使用组合缓存注解
* @param lastName
* @return
*/
@Caching(
cacheable = {/* 缓存根据 lastName 作为 key 获取缓存中的数据 */
@Cacheable(value = "emp", key = "#lastName")
},
put = { /* 会将返回后的结果按 email 和 id 作为 缓存的 key 保存 */
@CachePut(value = "emp", key = "#result.email"),
@CachePut(value = "emp", key = "#result.id")
}
)
public Employee getEmpByLastName (String lastName){
Employee emp = employeeMapper.getEmpByLastName(lastName);
return emp;
}
}
缓存原理
springboot 如果要分析某个点, 可以根据 XXXAutoConfiguration 来切入
自动配置类: CacheAutoConfiguration
@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
/**
* {@link ImportSelector} to add {@link CacheType} configuration classes.
通过 ImportSelector 添加 配置类
*/
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;
}
}
}
通过 CacheType CacheConfigurations 可以看到加载了哪些配置类
final class CacheConfigurations {
private static final Map<CacheType, Class<?>> MAPPINGS;
static {
Map<CacheType, Class<?>> mappings = new HashMap<CacheType, Class<?>>();
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
addGuavaMapping(mappings);
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
MAPPINGS = Collections.unmodifiableMap(mappings);
}
@Deprecated
private static void addGuavaMapping(Map<CacheType, Class<?>> mappings) {
mappings.put(CacheType.GUAVA, GuavaCacheConfiguration.class);
}
}
通过在 application.properties 中设置
debug=true// 可以在控制台看到, springboot 默认选择的配置类
@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
SimpleCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
// 在容器中添加缓存管理器
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
// 用来保存 cache 组件
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
// 根据 cacheNames 和 value 指定缓存名获取缓存组件
@Override
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 为 null 就新创建
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
/**
* Create a new ConcurrentMapCache instance for the specified cache name.
* @param name the name of the cache
* @return the ConcurrentMapCache (or a decorator thereof)
*/
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
return new ConcurrentMapCache(name, new ConcurrentHashMap<Object, Object>(256),
isAllowNullValues(), actualSerialization);
}
}
ConcurrentMapCacheManager 获取 ConcurrentMapCache 缓存组件, ConcurrentMapCache 用来缓存数据
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final String name;
// 存取数据的集合
private final ConcurrentMap<Object, Object> store;
private final SerializationDelegate serialization;
@Override
protected Object lookup(Object key) {
return this.store.get(key);
}
// 获取
@SuppressWarnings("unchecked")
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
// Try efficient lookup on the ConcurrentHashMap first...
// 会调用父类 AbstractValueAdaptingCache get 方法,
// 然后子类 ConcurrentMapCache 重写 lookup 方法
ValueWrapper storeValue = get(key);
if (storeValue != null) {
return (T) storeValue.get();
}
// No value found -> load value within full synchronization.
synchronized (this.store) {
storeValue = get(key);
if (storeValue != null) {
return (T) storeValue.get();
}
T value;
try {
value = valueLoader.call();
}
catch (Throwable ex) {
throw new ValueRetrievalException(key, valueLoader, ex);
}
put(key, value);
return value;
}
}
// 保存
@Override
public void put(Object key, Object value) {
this.store.put(key, toStoreValue(value));
}
// 清除
@Override
public void evict(Object key) {
this.store.remove(key);
}
// 清空
@Override
public void clear() {
this.store.clear();
}
@Override
protected Object toStoreValue(Object userValue) {
Object storeValue = super.toStoreValue(userValue);
if (this.serialization != null) {
try {
return serializeValue(storeValue);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Failed to serialize
cache value '" + userValue + "'. Does it implement Serializable?", ex);
}
}
else {
return storeValue;
}
}
}
运行流程
* 运行流程:
* ① 进入 @Cacheable 目标方法前,ConcurrentMapCacheManager 根据 cacheNames 获取缓存组件
* ② 第一次获取没有 Cache 组件会自动创建
@Override
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;
}
* ③ 从缓存组件中获取数据, 如果没有指定 key 的值,根据 key 的生成策略,会使用方法参数作为 key;
* key 的生成策略:
* 默认使用 SimpleKeyGenerator 生成 key
* SimpleKeyGenerator 生成 key 策略:
* 如果没有参数,就返回一个 SimpleKey 对象
* 如果有一个参数, 直接使用这个参数
* 如果多参数,会将所有参数进行 hash 处理
* ④ 如果缓存中没有数据调用目标方法, 将目标方法返回结果放入缓存中
* 再次调用就可以使用缓存中的数据了
public class SimpleKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}