緩存
提到緩存,你能想到什么?一級緩存,二級緩存,web緩存,redis……
你所能想到的各種包羅萬象存在的打着緩存旗號存在的各種技術或者實現,無非都是宣揚緩存技術的優勢就是快,無需反復查詢等。
當然,這里要講的不是一級二級,也不是redis,而是Spring的緩存支持。當時基於工作上的業務場景,考慮需要用到緩存技術,但是並不清楚該用什么樣的緩存技術,起初甚至有想過把信息寫到redis中,然后讀redis的信息(現在想想,真是小題大做),后來發現Spring提供了緩存的解決方案——Spring Cache。雖然最終找到了一個更加簡便討巧的方式解決了問題,但是今天還是把自己零碎的Spring Cache知識稍稍梳理下。
這里大概介紹下業務場景:
- 現在有功能模塊一和功能模塊二,分別負責兩件事;
- 執行功能模塊二的時候,可以用到模塊一的執行結果,當然不用也可以,那就再執行一遍;
- 讀取功能模塊一執行后存放在Cache中的數據,這時候就省去重新執行模塊一的冗余操作
流程圖如下:
Spring Cache
有關Spring Cache的理論詳細介紹可以看官方文檔或者參閱《Spring實戰》第十三章,這里偏代碼實戰看看Spring Cache如何使用。
聲明啟用緩存
就像如果要啟用AOP需要添加
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
一樣,如果要用cache,也需要聲明
<cache:annotation-driven />
這樣還不夠,除此以外,我們還需要一個緩存管理器
<bean id="cacheManager"
class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="default" />
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="personCache" />
</set>
</property>
</bean>
這個緩存管理器是Spring緩存抽象的核心,可以繼承多個流行的緩存實現。從上面的聲明可以看出,這里聲明了ConcurrentMapCacheManager
管理器,從字面就可以看出其內容實現應該是通過ConcurrentHashMap
來做緩存的。(除了這個簡單的緩存管理器,當然還有如NoOpCacheManager、EhCacheCacheManager、RedisCacheManager、CompositeCacheManager等管理器)
這里的personCache
就是本例中用到的聲明的管理器
以上的xml聲明可以存放到一個spring-cache.xml中,完整內容如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven />
<bean id="personService" class="com.jackie.service.PersonService" />
<!-- generic cache manager -->
<bean id="cacheManager"
class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="default" />
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="personCache" />
</set>
</property>
</bean>
</beans>
編寫需要緩存的方法
- 在此之前,我們需要一個bean對象Person,其有
id
,age
,name
三個屬性
@Component
public class Person {
private int id;
private int age;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
- 准備需要緩存的接口方法
@Service("personService")
public class PersonService {
@Cacheable(value = "personCache")
public Person findPerson(int i) {
System.out.println("real query person object info");
Person person = new Person();
person.setId(i);
person.setAge(18);
person.setName("Jackie");
return person;
}
}
findPerson()
方法很簡單,主要是根據id查找到相應的Person對象。
這里第一行加上了打印信息,用於區分每次調用是否走緩存了。如果直接用的是緩存結果,則不會打印這句話,如果沒有緩存,則需要執行函數體,從而打印改行顯示。
@Cacheable
注解:主要用來配置方法,能夠根據方法的請求參數對其結果進行緩存。即當重復使用相同參數調用方法的時候,方法本身不會被調用執行,即方法本身被略過了,取而代之的是方法的結果直接從緩存中找到並返回了。簡而言之就是如果緩存有則取出,如果沒有則執行方法。
驗證
到此我們就可以驗證了。編寫代碼如下
public class SpringCache {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring-cache.xml");// 加載 spring 配置文件
PersonService personService = (PersonService) context.getBean("personService");
// 第一次查詢,應該真實查詢
System.out.println(personService.findPerson(18));
// 第二次查詢,應該直接返回緩存的值
System.out.println(personService.findPerson(18));
}
}
最終執行結果如下
real query person object info
Person{id=18, age=18, name='Jackie'}
Person{id=18, age=18, name='Jackie'}
可以看出第一次結果顯示前打印出了real query person object info
說明真實執行了findPerson()
方法;
第二次打印的結果沒有包含這行信息,說明是從緩存中直接取的數據,而且通過調試打斷點確實發現第二次並未進入findPerson()
方法。
@CacheEvict
之所以能夠不用執行方法直接拿到緩存結果,是因為將執行結果存到了緩存中。既然有存緩存的過程,自然有刪除緩存的過程,而這個操作就要用到@CacheEvict
這個注解了。
還是通過實例來看,這里舉出兩個例子——根據方法清除緩存和清除全部緩存。
我們在PersonService中分別加上這兩個接口方法
@CacheEvict(value = "personCache", key = "#person.getId()")
public void updatePerson(Person person) {
System.out.println("update: " + person.getName());
}
@CacheEvict(value = "personCache", allEntries = true)
public void reload() {
}
稍稍解釋下,當updatePerson()
方法被調用時,其會根據key來取出相應的緩存記錄刪除;而對於方法reload()
則是在該方法被調用時,清空所有緩存記錄。
測試代碼如下
Person lucy = personService.findPerson(1);
Person lily = personService.findPerson(2);
lucy.setName("Tracy");
personService.updatePerson(lucy);
System.out.println(personService.findPerson(1));
System.out.println(personService.findPerson(2));
personService.reload();
System.out.println(personService.findPerson(1));
System.out.println(personService.findPerson(2));
執行結果如下
real query person object info
real query person object info
update: Tracy
real query person object info
Person{id=1, age=18, name='Jackie'}
Person{id=2, age=18, name='Jackie'}
real query person object info
Person{id=1, age=18, name='Jackie'}
real query person object info
Person{id=2, age=18, name='Jackie'}
第1,2行分別是因為針對id=1,2都是第一次查詢,這時候沒有緩存記錄,所以都真實執行了方法findPerson()
;
第3行是調用方法updatePerson()
打印的信息
第4,5行是再次查詢id=1的Person信息,這里之所以打印出real query person object info
是因為之前調用了id=1時的updatePerson()
方法,該方法觸發,根據getId()
找到id=1的緩存記錄進行刪除,所以這時候查找id=1的時候,緩存記錄已經不存在,故而要執行findPerson()
方法。
第6行是再次查詢id=2的Person信息,因為之前已經查過一次,並且沒有刪除過這條緩存記錄,所以再次查找時,直接用緩存結果。
第7,8行是再次查詢id=1的Person信息,注意再次之前,執行了reload()
方法,這個方法會清楚所有的緩存信息,所以對於id=1和2的緩存記錄都已經清空,這里就又重新執行findPerson()
方法
第9,10行同7,8
當然,關於Spring Cache還有很多靈活的應用以及功能強大的注解和用法,這里只是通過實例了解Spring Cache有什么用,如何用。
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。