个人博客:haichenyi.com。感谢关注
??缓存是每个项目都用到的为了提高接口响应效率,降低数据库的查询压力,从而提高系统性能。所以,缓存对于一个项目来说是至关重要的。
简介
??spring 3+之后,就定义了CacheManager和Cache接口来统一不同的缓存技术。
- CacheManager:缓存管理器,用于管理各种Cache缓存组件
- Cahce:定义了各种操作,Spring在Cache接口下提供了各种xxCache的实现;比如:ConcurrentMapCache,RedisCache,JCacheCache等等
源码解析
??这里我以默认缓存为例:ConcurrentMapCache
??上面说了,缓存只用配置就可以直接使用,所以,配置,一说到配置,我们就会想到之前说的自动配置类AutoConfigure。如下图:
??我们看到,自动配置类目录下面有一个cache包,这就是缓存自动配置的包,我们熟悉的类就有CacheProperties这个类,我们点进去看:
??我们熟悉的prefix就在这里了,也就是我们在全局配置类里面的键。
??这里,配置的一般都是这个类里面的全局变量,我把这个CacheType给框出来了(下面的cacheNames这个list变量也很重要),上面的注释的意思是说,缓存类型,默认情况下,是根据环境自动检测的。
??我们上面说到CacheManager和Cache接口是用来同意管理不同的缓存技术。不同的,也就是指的我们这里的缓存类型。所以,我们这里的缓存类型肯定有好几种,然后,这里又讲是根据环境自动检测的。也就是我们配置好的。也就是我们在全局配置类里面配置好的
spring.cache.xxx=xxx
??既然是配置,肯定是定义了之后才能配置的,没定义,怎么可能配置,spring又不是神。那,定义了那些种类的缓存技术呢?我们看一下CacheType类
??定义的,就这10种:GENERIC,JCACHE,EHCACHE,HAZELCAST,INFINISPAN,COUCHBASE,REDIS,CAFFEINE,SIMPLE,NONE。作为一个初学者,我想一眼看过去,我们能看到熟悉的Redis,学后天,Redis框架肯定要学。
??我们现在,什么都没有配置,默认的缓存类型就是这个Simple,可以在Cache目录下面的CacheAutoConfiguration类中看到selectImports方法,在这里打断点,我们debug模式运行,我们就能看到它导入的缓存类型,然后,每个进行检测,看匹配哪一个。
??我们就看一下SimpleCacheConfiguration是怎么实现的
??我们看到了,SimpleCacheConfiguration默认使用的是:ConcurrentMapCacheManager,我们,看一下这个Manager是怎么实现的
??这类,实现的就是CacheManager接口,而CacheManager接口就只有两个方法,就是上图中的两个方法setCacheNames 和 getCache
??先说一下数据是怎么缓存的,缓存是一个容器,这个容器怎么获取的呢?就是通过这里的name,name是获取这个容器的key,然后里面的数据存放形式,都是key-value的形式存放的。这个key也是我们定义的,value就是数据库查询的数据。如下图。
??这个setCacheNames,就是,我们配置的cacheNames的值,它会获取好之后,将这些值封装成list,通过setCacheNames方法赋值给这里的变量cacheMap。我们可以看一下这个变量:
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
??然后就是这个getCache方法,上面的setCacheNames以cacheName为键去存这个Cache,这个就是刚好相反,获取方法,通过cacheName去获取这个Cache。
??然后,我们现在是获取到了这个缓存容器,那么,我们要怎么从这个缓存容器中去获取我们对应的数据呢?
??我们看到上面那个变量是Map是以String为键,以Cache为值,我们最开始说过了,Cache和CacheManager是用来管理不同缓存技术的接口,所以,这里的值不可能是一个接口对象,肯定是它的实现类,我们再仔细看上面两个方法的实现类,我们会看到
//setCacheNames方法
this.cacheMap.put(name, createConcurrentMapCache(name));
//getCache方法
cache = createConcurrentMapCache(name);
??很明显,这里就是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<>(256),
isAllowNullValues(), actualSerialization);
}
??所以,它这里是new的ConcurrentMapCache肯定是Cache的实现类。我们看到这个构造方法,第二个参数是一个hashMap,而我们的缓存容器里面也是以键值对的方式存储数据的。我们再看这个ConcurrentMapCache
??一共就只有三个全局变量,第一个name,是我们前面传过来的cacheName,第二个是Map<object,object>类型,第三个是SerializationDelegate类型的变量,序列化的一个什么东西。排除法判断,只可能这个Map就是用来存放我们的缓存数据的。我们搜索这个变量,我们会看到如下几个方法:
//获取缓存数据
@Override
@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}
//存放
@Override
public void put(Object key, @Nullable Object value) {
this.store.put(key, toStoreValue(value));
}
//通过key移除数据
@Override
public void evict(Object key) {
this.store.remove(key);
}
//清空所有数据
@Override
public void clear() {
this.store.clear();
}
//清空所有数据
@Override
public boolean invalidate() {
boolean notEmpty = !this.store.isEmpty();
this.store.clear();
return notEmpty;
}
??至此,缓存怎么存放,怎么获取都说完了。
用法
??与前面差不多,都是在启动类上面开启,在方法上面标记注解就行了
- @EnableCaching:在启动类上,开启基于注解的缓存
- @Cacheable:标在方法上,返回的结果会进行缓存(先查缓存中的结果,没有则调用方法并将结果放到缓存中)
- @CachePut:保证方法被调用后,又将对应缓存中的数据更新(先调用方法,调完方法再将结果放到缓存)
- @CacheEvict:清除缓存
??@Cacheable,@CachePut,@CacheEvict三个注解都有几个重要的属性:
- cacheNames:缓存的名字。
- key: 作为缓存中的Key值,可以使用SpEL表达式指定(不指定,key就是参数值),缓存结果是方法返回值
??上面两个属性是前面我们一直都在强调的比较重要的属性,然后,清除缓存的注解中还有两个属性需要了解:
- allEntries =true : 指定清除这个缓存中所有数据。
- beforeInvocation = true : true在方法之前执行;默认false在方法之后执行,出现一场则不会清除缓存
??我这里值贴出来缓存相关的类:
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@Cacheable(cacheNames = "user", key = "#id")
public User getUser(Integer id) {
return userMapper.getUserById(id);
}
@CachePut(cacheNames = "user", key = "#result.id")
public User updateUser(User user) {
userMapper.updateUser(user);
return user;
}
@CacheEvict(cacheNames = "user", key = "#result")
public Integer deleteUser(Integer id) {
userMapper.deleteUserById(id);
return id;
}
}
总结
??第一步:默认采用的是SimpleCacheConfiguration 使用 ConcurrentMapCacheManager
??第二步:getCache 获取的是 ConcurrentMapCache 缓存对象进行存取数据,它使用ConcurrentMap<Object,Object>对象进行缓存数据。
@Cacheable(cacheNames = "user", key = "#id")
第一次请求时:
??第三步:当发送第一次请求时,会从getCache(name)中获取,看有没有ConcurrentMapCache缓存对象,如果没有 则创建出来, 并且创建出来的key就是通过
@Cacheable(cacheNames = "user")标识的name值
??第四步:接着会从ConcurrentMapCache里面调用lookup获取缓存数据,通过key值获取的,
默认采用的是service方法中的参数值,如果缓存中没有获取到,则调用目标方法进行获取数据(即从数据库中查询),获取之后则再将它 放到缓存中(key=参数值,value=返回值)
第二次请求时:
??第五步:如果再次调用 则还是先ConcurrentMapCacheManager.getCache()获取缓存对象,如果有则直接返回, 如果没有则创建
??第六步:然后再调用 ConcurrentMapCache.lookup方法从缓存中获取数据, 如果缓存有数据则直接响应回去,不 会再去调用目标方法
第三次请求与第二次一样
如果缓存中没有缓存管理器,则与第一次请求一致