淺談Spring boot cache使用和原理
緩存要解決的問(wèn)題:一個(gè)程序的瓶頸在于數(shù)據(jù)庫(kù),我們也知道內(nèi)存的速度是大大快于硬盤(pán)的速度的。當(dāng)我們需要重復(fù)地獲取相同的數(shù)據(jù)的時(shí)候,我們一次又一次的請(qǐng)求數(shù)據(jù)庫(kù)或者遠(yuǎn)程服務(wù),導(dǎo)致大量的時(shí)間耗費(fèi)在數(shù)據(jù)庫(kù)查詢或者遠(yuǎn)程方法調(diào)用上,導(dǎo)致程序性能的惡化,這便是數(shù)據(jù)緩存要解決的問(wèn)題。
類(lèi)似的緩存技術(shù)有:Redis、EhCache、Guava等,現(xiàn)在一般常用的為Redis。
Spring 3.1 引入了激動(dòng)人心的基于注釋?zhuān)╝nnotation)的緩存(cache)技術(shù),它本質(zhì)上不是一個(gè)具體的緩存實(shí)現(xiàn)方案(例如EHCache 或者 OSCache),而是一個(gè)對(duì)緩存使用的抽象,通過(guò)在既有代碼中添加少量它定義的各種 annotation,即能夠達(dá)到緩存方法的返回對(duì)象的效果。
Spring 的緩存技術(shù)還具備相當(dāng)?shù)撵`活性,不僅能夠使用 SpEL(Spring Expression Language)來(lái)定義緩存的 key 和各種 condition,還提供開(kāi)箱即用的緩存臨時(shí)存儲(chǔ)方案,也支持和主流的專(zhuān)業(yè)緩存例如 EHCache 集成。
其特點(diǎn)總結(jié)如下:
1. 通過(guò)少量的配置 annotation 注釋即可使得既有代碼支持緩存
2. 支持開(kāi)箱即用 Out-Of-The-Box,即不用安裝和部署額外第三方組件即可使用緩存
3. 支持 Spring Express Language,能使用對(duì)象的任何屬性或者方法來(lái)定義緩存的 key 和 condition
4. 支持 AspectJ,并通過(guò)其實(shí)現(xiàn)任何方法的緩存支持
5. 支持自定義 key 和自定義緩存管理者,具有相當(dāng)?shù)撵`活性和擴(kuò)展性
一、Spring boot cache原理
第一步、自動(dòng)配置類(lèi);
自動(dòng)啟動(dòng)類(lèi):CacheAutoConfiguration
屬性配置:CacheProperties
主啟動(dòng)類(lèi)添加:@EnableCaching注解
cache POM添加:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
第二步、從緩存的配置類(lèi) 中獲取 多個(gè)cache
CacheConfigurationImportSelector.selectImports()方法獲取
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;
}
}
獲取結(jié)果:SimpleCacheConfiguration 默認(rèn)cache
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默認(rèn)】 org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
第三步:SimpleCacheConfiguration.cacheManager()
此方法中給容器中注冊(cè)了一個(gè)CacheManager組件:類(lèi)型為ConcurrentMapCacheManager
@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);
}
第四步:查看獲取緩存方法getCache()
ConcurrentMapCacheManager 類(lèi)里,數(shù)據(jù)都存儲(chǔ)到為ConcurrentMap 中
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name); //cacheMap 為ConcurrentMap 類(lèi)型,獲取一個(gè)cache組件
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name); //cahceMap不為空獲取
if (cache == null) {
//可以獲取或者創(chuàng)建ConcurrentMapCache類(lèi)型的緩存組件;他的作用將數(shù)據(jù)保存在ConcurrentMap中;
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache); //ConcurrentMapCache.lookup();
}
}
}
return cache;
}
二、Cacheable運(yùn)行流程:
@Cacheable: 1、方法運(yùn)行之前,先去查詢Cache(緩存組件),按照cacheNames指定的名字獲取; (CacheManager先獲取相應(yīng)的緩存),第一次獲取緩存如果沒(méi)有Cache組件會(huì)自動(dòng)創(chuàng)建。 2、去Cache中查找緩存的內(nèi)容(ConcurrentMapCache.lookup()方法中去查找),使用一個(gè)key,默認(rèn)就是方法的參數(shù); key是按照某種策略生成的;默認(rèn)是使用keyGenerator生成的,默認(rèn)使用SimpleKeyGenerator生成key; SimpleKeyGenerator生成key的默認(rèn)策略; 如果沒(méi)有參數(shù);key=new SimpleKey(); 如果有一個(gè)參數(shù):key=參數(shù)的值 如果有多個(gè)參數(shù):key=new SimpleKey(params);
//這個(gè)方法 SimpleKeyGenerator.generateKey() 方法生成key
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) { //如果只有一個(gè)參數(shù),直接返回這個(gè)參數(shù)為key
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
3、沒(méi)有查到緩存就調(diào)用目標(biāo)方法; 4、將目標(biāo)方法返回的結(jié)果,放進(jìn)緩存中ConcurrentMapCache.put();
@Cacheable標(biāo)注的方法執(zhí)行之前先來(lái)檢查緩存中有沒(méi)有這個(gè)數(shù)據(jù),默認(rèn)按照參數(shù)的值作為key去查詢緩存, 如果沒(méi)有就運(yùn)行方法并將結(jié)果放入緩存;以后再來(lái)調(diào)用就可以直接使用緩存中的數(shù)據(jù);
核心: 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】組件 2)、key使用keyGenerator生成的,默認(rèn)是SimpleKeyGenerator
詳細(xì)執(zhí)行流程:ConcurrentMapCache.lookup()上斷點(diǎn)查看,執(zhí)行過(guò)程
//第一步CacheAspectSupport 中execute()
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)
//第二步 CacheAspectSupport
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
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;
}
//第三步:CacheAspectSupport.findInCaches()
//第四步:AbstractCacheInvoker.doGet()
//第五步:AbstractValueAdaptingCache.get();
@Override
public ValueWrapper get(Object key) {
Object value = lookup(key);
return toValueWrapper(value);
}
// 第六步:ConcurrentMapCache.lookup(); 從ConcurrentMap 中根據(jù)key獲取值
@Override
protected Object lookup(Object key) {
return this.store.get(key);
}
三、Cacheable 注解的幾個(gè)屬性:
1、cacheNames/value:指定緩存組件的名字;將方法的返回結(jié)果放在哪個(gè)緩存中,是數(shù)組的方式,可以指定 多個(gè)緩存;
2、key:緩存數(shù)據(jù)使用的key;可以用它來(lái)指定。默認(rèn)是使用方法參數(shù)的值 1-方法的返回值
編寫(xiě)SpEL; #i d;參數(shù)id的值 #a0 #p0 #root.args[0]
getEmp[2]
3、keyGenerator:key的生成器;可以自己指定key的生成器的組件id
key/keyGenerator:二選一使用;
4、cacheManager:指定緩存管理器;或者cacheResolver指定獲取解析器
5、condition:指定符合條件的情況下才緩存;
,condition = "#id>0"
condition = "#a0>1":第一個(gè)參數(shù)的值》1的時(shí)候才進(jìn)行緩存
6、unless:否定緩存;當(dāng)unless指定的條件為true,方法的返回值就不會(huì)被緩存;可以獲取到結(jié)果進(jìn)行判斷
unless = "#result == null"
unless = "#a0==2":如果第一個(gè)參數(shù)的值是2,結(jié)果不緩存;
7、sync:是否使用異步模式;異步模式的情況下unless不支持
四、Cache使用:
1.Cacheable的使用
@Cacheable(value = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/)
public Employee getEmp(Integer id){
System.out.println("查詢"+id+"號(hào)員工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
2.自定義keyGenerator:
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
3.CachePut的使用:更新緩存
/**
* @CachePut:既調(diào)用方法,又更新緩存數(shù)據(jù);同步更新緩存
* 修改了數(shù)據(jù)庫(kù)的某個(gè)數(shù)據(jù),同時(shí)更新緩存;
* 運(yùn)行時(shí)機(jī):
* 1、先調(diào)用目標(biāo)方法
* 2、將目標(biāo)方法的結(jié)果緩存起來(lái)
*
* 測(cè)試步驟:
* 1、查詢1號(hào)員工;查到的結(jié)果會(huì)放在緩存中;
* key:1 value:lastName:張三
* 2、以后查詢還是之前的結(jié)果
* 3、更新1號(hào)員工;【lastName:zhangsan;gender:0】
* 將方法的返回值也放進(jìn)緩存了;
* key:傳入的employee對(duì)象 值:返回的employee對(duì)象;
* 4、查詢1號(hào)員工?
* 應(yīng)該是更新后的員工;
* key = "#employee.id":使用傳入的參數(shù)的員工id;
* key = "#result.id":使用返回后的id
* @Cacheable的key是不能用#result
* 為什么是沒(méi)更新前的?【1號(hào)員工沒(méi)有在緩存中更新】
*
*/
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
4.CacheEvict 緩存清除
/**
* @CacheEvict:緩存清除
* key:指定要清除的數(shù)據(jù)
* allEntries = true:指定清除這個(gè)緩存中所有的數(shù)據(jù)
* beforeInvocation = false:緩存的清除是否在方法之前執(zhí)行
* 默認(rèn)代表緩存清除操作是在方法執(zhí)行之后執(zhí)行;如果出現(xiàn)異常緩存就不會(huì)清除
*
* beforeInvocation = true:
* 代表清除緩存操作是在方法運(yùn)行之前執(zhí)行,無(wú)論方法是否出現(xiàn)異常,緩存都清除
*
*
*/
@CacheEvict(value="emp",beforeInvocation = true,key = "#id")
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.deleteEmpById(id);
int i = 10/0;
}
5.Caching 復(fù)雜配置
// @Caching 定義復(fù)雜的緩存規(guī)則
@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);
}
6.CacheConfig緩存清除
@CacheConfig(cacheNames="emp",cacheManager = "employeeCacheManager") //抽取緩存的公共配置
@Service
public class EmployeeService {
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot 對(duì)象存儲(chǔ) MinIO的詳細(xì)過(guò)程
MinIO 是一個(gè)基于 Go 實(shí)現(xiàn)的高性能、兼容 S3 協(xié)議的對(duì)象存儲(chǔ),它適合存儲(chǔ)海量的非結(jié)構(gòu)化的數(shù)據(jù),這篇文章主要介紹了SpringBoot 對(duì)象存儲(chǔ) MinIO,需要的朋友可以參考下2023-07-07
java.net.ConnectException異常的正確解決方法(親測(cè)有效!)
java.net.ConnectException異常是與網(wǎng)絡(luò)相關(guān)的最常見(jiàn)的Java異常之一,建立從客戶端應(yīng)用程序到服務(wù)器的TCP連接時(shí),我們可能會(huì)遇到它,這篇文章主要給大家介紹了關(guān)于java.net.ConnectException異常的正確解決方法,需要的朋友可以參考下2024-01-01
將字符串?dāng)?shù)字格式化為樣式1,000,000,000的方法
這篇文章主要介紹了將字符串?dāng)?shù)字格式化為樣式1,000,000,000的方法,有需要的朋友可以參考一下2014-01-01
mybatis如何實(shí)現(xiàn)in傳入數(shù)組查詢
這篇文章主要介紹了mybatis如何實(shí)現(xiàn)in傳入數(shù)組查詢方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
一文教會(huì)你如何從0到1搭建一個(gè)SpringBoot項(xiàng)目
今天剛好學(xué)習(xí)到SpringBoot,就順便記錄一下吧,下面這篇文章主要給大家介紹了關(guān)于如何從0到1搭建一個(gè)SpringBoot項(xiàng)目的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01
SpringBoot參數(shù)校驗(yàn)的方法總結(jié)
今天帶大家學(xué)習(xí)SpringBoot參數(shù)校驗(yàn)的方法,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下2021-05-05
Spring MVC 處理一個(gè)請(qǐng)求的流程
Spring MVC是Spring系列框架中使用頻率最高的部分。不管是Spring Boot還是傳統(tǒng)的Spring項(xiàng)目,只要是Web項(xiàng)目都會(huì)使用到Spring MVC部分。因此程序員一定要熟練掌握MVC部分。本篇博客簡(jiǎn)要分析Spring MVC處理一個(gè)請(qǐng)求的流程。2021-02-02
解決OpenFeign遠(yuǎn)程調(diào)用返回的對(duì)象總是null問(wèn)題
OpenFeign在SpringCloud中用于遠(yuǎn)程調(diào)用,配置簡(jiǎn)單,在使用Ribbon或Hystrix時(shí),需要注意path參數(shù)必須以/開(kāi)頭,否則回參會(huì)是null2024-11-11

