概述
Spring 3.1 引入了激動(dòng)人心的基于注解(annotation)的緩存(cache)技術(shù)族阅,它本質(zhì)上不是一個(gè)具體的緩存實(shí)現(xiàn)方案(比如EHCache 或者 OSCache),而是一個(gè)對(duì)緩存使用的抽象坦刀,通過在既有代碼中加入少量它定義的各種注解,即能夠達(dá)到緩存方法的返回對(duì)象的效果蔬咬。
Spring 的緩存技術(shù)還具備相當(dāng)?shù)撵`活性。不僅能夠使用SpEL(Spring Expression Language)來(lái)定義緩存的 key 和各種 condition林艘,還提供開箱即用的緩存暫時(shí)存儲(chǔ)方案,也支持和主流的專業(yè)緩存比如 EHCache 集成狐援。
其特點(diǎn)總結(jié)例如以下:
- 通過少量的配置 annotation 注解就可以使得既有代碼支持緩存
- 支持開箱即用 Out-Of-The-Box钢坦,即不用安裝和部署額外第三方組件就可以使用緩存
- 支持 Spring Express Language啥酱,能使用對(duì)象的不論什么屬性或者方法來(lái)定義緩存的 key 和 condition
- 支持 AspectJ,并通過事實(shí)上現(xiàn)不論什么方法的緩存支持
- 支持自己定義 key 和自己定義緩存管理者镶殷,具有相當(dāng)?shù)撵`活性和擴(kuò)展性
一、 自己實(shí)現(xiàn)緩存
這里先展示一個(gè)自己定義的緩存實(shí)現(xiàn)绘趋,即不用第三方的組件來(lái)實(shí)現(xiàn)某種對(duì)象的內(nèi)存緩存颤陶。
場(chǎng)景例如以下:
對(duì)一個(gè)賬號(hào)查詢方法做緩存陷遮,賬號(hào)名稱為 key,賬號(hào)對(duì)象為 value帽馋,當(dāng)以同樣的賬號(hào)名稱查詢賬號(hào)的時(shí)候闲坎,直接從緩存中返回結(jié)果茬斧。否則更新緩存。賬號(hào)查詢服務(wù)還支持 reload 緩存(即清空緩存)
首先定義一個(gè)實(shí)體類:賬號(hào)類项秉,具備主要的 id 和 name 屬性。且具備 getter 和 setter 方法
public class Account {
private int id;
private String name;
public Account(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后定義一個(gè)緩存管理器娄蔼,這個(gè)管理器負(fù)責(zé)實(shí)現(xiàn)緩存邏輯底哗,支持對(duì)象的添加、改動(dòng)和刪除锚沸,支持值對(duì)象的泛型。
public class CacheContext<T> {
private Map<String, T> cache = Maps.newConcurrentMap();
public T get(String key){
return cache.get(key);
}
public void addOrUpdateCache(String key,T value) {
cache.put(key, value);
}
// 依據(jù) key 來(lái)刪除緩存中的一條記錄
public void evictCache(String key) {
if(cache.containsKey(key)) {
cache.remove(key);
}
}
// 清空緩存中的全部記錄
public void evictCache() {
cache.clear();
}
}
好哗蜈,如今我們有了實(shí)體類和一個(gè)緩存管理器,還須要一個(gè)提供賬號(hào)查詢的服務(wù)類距潘。此服務(wù)類使用緩存管理器來(lái)支持賬號(hào)查詢緩存炼列。例如以下:
@Service
public class AccountService1 {
private final Logger logger = LoggerFactory.getLogger(AccountService1.class);
@Resource
private CacheContext<Account> accountCacheContext;
public Account getAccountByName(String accountName) {
Account result = accountCacheContext.get(accountName);
if (result != null) {
logger.info("get from cache... {}", accountName);
return result;
}
Optional<Account> accountOptional = getFromDB(accountName);
if (!accountOptional.isPresent()) {
throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName));
}
Account account = accountOptional.get();
accountCacheContext.addOrUpdateCache(accountName, account);
return account;
}
public void reload() {
accountCacheContext.evictCache();
}
private Optional<Account> getFromDB(String accountName) {
logger.info("real querying db... {}", accountName);
//Todo query data from database
return Optional.fromNullable(new Account(accountName));
}
}
現(xiàn)在我們寫一個(gè)測(cè)試類俭尖,用于測(cè)試剛才的緩存是否有效
public class AccountService1Test {
private AccountService1 accountService1;
private final Logger logger = LoggerFactory.getLogger(AccountService1Test.class);
@Before
public void setUp() throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
accountService1 = context.getBean("accountService1", AccountService1.class);
}
@Test
public void testInject(){
assertNotNull(accountService1);
}
@Test
public void testGetAccountByName() throws Exception {
accountService1.getAccountByName("accountName");
accountService1.getAccountByName("accountName");
accountService1.reload();
logger.info("after reload ....");
accountService1.getAccountByName("accountName");
accountService1.getAccountByName("accountName");
}
}
依照分析,運(yùn)行結(jié)果應(yīng)該是:首先從數(shù)據(jù)庫(kù)查詢洞翩,然后直接返回緩存中的結(jié)果,重置緩存后骚亿,應(yīng)該先從數(shù)據(jù)庫(kù)查詢。然后返回緩存中的結(jié)果. 查看程序運(yùn)行的日志例如以下:
00:53:17.166 [main] INFO c.r.s.cache.example1.AccountService - real querying db... accountName
00:53:17.168 [main] INFO c.r.s.cache.example1.AccountService - get from cache... accountName
00:53:17.168 [main] INFO c.r.s.c.example1.AccountServiceTest - after reload ....
00:53:17.168 [main] INFO c.r.s.cache.example1.AccountService - real querying db... accountName
00:53:17.169 [main] INFO c.r.s.cache.example1.AccountService - get from cache... accountName
能夠看出我們的緩存起效了循未,可是這樣的自己定義的緩存方案有例如以下劣勢(shì):
- 緩存代碼和業(yè)務(wù)代碼耦合度太高。如上面的樣例的妖,AccountService 中的 getAccountByName()方法中有了太多緩存的邏輯,不便于維護(hù)和變更
- 不靈活足陨,這樣的緩存方案不支持依照某種條件的緩存,比方僅僅有某種類型的賬號(hào)才須要緩存墨缘,這樣的需求會(huì)導(dǎo)致代碼的變更
- 緩存的存儲(chǔ)這塊寫的比較死,不能靈活的切換為使用第三方的緩存模塊
二镊讼、Spring cache實(shí)現(xiàn)緩存
我們對(duì)AccountService1 進(jìn)行改動(dòng)宽涌。創(chuàng)建AccountService2:
@Service
public class AccountService2 {
private final Logger logger = LoggerFactory.getLogger(AccountService2.class);
// 使用了一個(gè)緩存名叫 accountCache
@Cacheable(value="accountCache")
public Account getAccountByName(String accountName) {
// 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯蝶棋,直接實(shí)現(xiàn)業(yè)務(wù)
logger.info("real querying account... {}", accountName);
Optional<Account> accountOptional = getFromDB(accountName);
if (!accountOptional.isPresent()) {
throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName));
}
return accountOptional.get();
}
private Optional<Account> getFromDB(String accountName) {
logger.info("real querying db... {}", accountName);
//Todo query data from database
return Optional.fromNullable(new Account(accountName));
}
}
我們注意到在上面的代碼中有一行:
@Cacheable(value="accountCache")
這個(gè)注解的意思是,當(dāng)調(diào)用這種方法的時(shí)候玩裙。會(huì)從一個(gè)名叫 accountCache 的緩存中查詢段直,假設(shè)沒有,則運(yùn)行實(shí)際的方法(即查詢數(shù)據(jù)庫(kù))溶诞,并將運(yùn)行的結(jié)果存入緩存中。否則返回緩存中的對(duì)象螺垢。這里的緩存中的 key 就是參數(shù) accountName喧务,value 就是 Account 對(duì)象甩苛。“accountCache”緩存是在 spring*.xml 中定義的名稱。我們還須要一個(gè) spring 的配置文件來(lái)支持基于注解的緩存
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<context:component-scan base-package="com.rollenholt.spring.cache"/>
<context:annotation-config/>
<cache:annotation-driven/>
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="default"/>
</bean>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="accountCache"/>
</bean>
</set>
</property>
</bean>
</beans>
注意這個(gè) spring 配置文件有一個(gè)關(guān)鍵的支持緩存的配置項(xiàng):
<cache:annotation-driven />
這個(gè)配置項(xiàng)缺省使用了一個(gè)名字叫 cacheManager 的緩存管理器讯蒲,這個(gè)緩存管理器有一個(gè) spring 的缺省實(shí)現(xiàn),即 org.springframework.cache.support.SimpleCacheManager墨林。這個(gè)緩存管理器實(shí)現(xiàn)了我們剛剛自己定義的緩存管理器的邏輯,它須要配置一個(gè)屬性 caches旭等,即此緩存管理器管理的緩存集合酌呆,除了缺省的名字叫 default 的緩存搔耕,我們還自己定義了一個(gè)名字叫 accountCache 的緩存,使用了缺省的內(nèi)存存儲(chǔ)方案 ConcurrentMapCacheFactoryBean弃榨,它是基于 java.util.concurrent.ConcurrentHashMap 的一個(gè)內(nèi)存緩存實(shí)現(xiàn)方案菩收。
然后我們編寫測(cè)試程序:
public class AccountService2Test {
private AccountService2 accountService2;
private final Logger logger = LoggerFactory.getLogger(AccountService2Test.class);
@Before
public void setUp() throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
accountService2 = context.getBean("accountService2", AccountService2.class);
}
@Test
public void testInject(){
assertNotNull(accountService2);
}
@Test
public void testGetAccountByName() throws Exception {
logger.info("first query...");
accountService2.getAccountByName("accountName");
logger.info("second query...");
accountService2.getAccountByName("accountName");
}
}
以上測(cè)試代碼主要進(jìn)行了兩次查詢鲸睛。第一次應(yīng)該會(huì)查詢數(shù)據(jù)庫(kù),第二次應(yīng)該返回緩存官辈。不再查數(shù)據(jù)庫(kù),我們運(yùn)行一下拳亿∏绻桑看看結(jié)果
01:10:32.435 [main] INFO c.r.s.c.example2.AccountService2Test - first query...
01:10:32.457 [main] INFO c.r.s.cache.example2.AccountService2 - real querying db... accountName
01:10:32.458 [main] INFO c.r.s.c.example2.AccountService2Test - second query...
01:10:32.456 [main] INFO c.r.s.cache.example2.AccountService2 - real querying db... accountName
能夠看出我們?cè)O(shè)置的基于注解的緩存起作用了肺魁,而在 AccountService.java 的代碼中。我們沒有看到不論什么的緩存邏輯代碼。僅僅有一行注解:@Cacheable(value="accountCache")胡桨,就實(shí)現(xiàn)了主要的緩存方案。
三昧谊、清空緩存
當(dāng)賬號(hào)數(shù)據(jù)發(fā)生變更刽虹,那么必須要清空某個(gè)緩存呢诬,另外還須要定期的清空全部緩存,以保證緩存數(shù)據(jù)的可靠性尚镰。
為了加入清空緩存的邏輯阀圾。我們僅僅要對(duì) AccountService2.java 進(jìn)行改動(dòng)狗唉,從業(yè)務(wù)邏輯的角度上看,它有兩個(gè)須要清空緩存的地方
- 當(dāng)外部調(diào)用更新了賬號(hào)分俯,則我們須要更新此賬號(hào)相應(yīng)的緩存
- 當(dāng)外部調(diào)用說(shuō)明又一次載入肾筐,則我們須要清空全部緩存
我們?cè)贏ccountService2的基礎(chǔ)上進(jìn)行改動(dòng)缸剪,改動(dòng)為AccountService3,代碼例如以下:
@Service
public class AccountService3 {
private final Logger logger = LoggerFactory.getLogger(AccountService3.class);
// 使用了一個(gè)緩存名叫 accountCache
@Cacheable(value="accountCache")
public Account getAccountByName(String accountName) {
// 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯杏节,直接實(shí)現(xiàn)業(yè)務(wù)
logger.info("real querying account... {}", accountName);
Optional<Account> accountOptional = getFromDB(accountName);
if (!accountOptional.isPresent()) {
throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName));
}
return accountOptional.get();
}
@CacheEvict(value="accountCache",key="#account.getName()")
public void updateAccount(Account account) {
updateDB(account);
}
@CacheEvict(value="accountCache",allEntries=true)
public void reload() {
}
private void updateDB(Account account) {
logger.info("real update db...{}", account.getName());
}
private Optional<Account> getFromDB(String accountName) {
logger.info("real querying db... {}", accountName);
//Todo query data from database
return Optional.fromNullable(new Account(accountName));
}
}
我們的測(cè)試代碼例如以下:
public class AccountService3Test {
private AccountService3 accountService3;
private final Logger logger = LoggerFactory.getLogger(AccountService3Test.class);
@Before
public void setUp() throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
accountService3 = context.getBean("accountService3", AccountService3.class);
}
@Test
public void testGetAccountByName() throws Exception {
logger.info("first query.....");
accountService3.getAccountByName("accountName");
logger.info("second query....");
accountService3.getAccountByName("accountName");
}
@Test
public void testUpdateAccount() throws Exception {
Account account1 = accountService3.getAccountByName("accountName1");
logger.info(account1.toString());
Account account2 = accountService3.getAccountByName("accountName2");
logger.info(account2.toString());
account2.setId(121212);
accountService3.updateAccount(account2);
// account1會(huì)走緩存
account1 = accountService3.getAccountByName("accountName1");
logger.info(account1.toString());
// account2會(huì)查詢db
account2 = accountService3.getAccountByName("accountName2");
logger.info(account2.toString());
}
@Test
public void testReload() throws Exception {
accountService3.reload();
// 這2行查詢數(shù)據(jù)庫(kù)
accountService3.getAccountByName("somebody1");
accountService3.getAccountByName("somebody2");
// 這兩行走緩存
accountService3.getAccountByName("somebody1");
accountService3.getAccountByName("somebody2");
}
}
在這個(gè)測(cè)試代碼中我們重點(diǎn)關(guān)注testUpdateAccount()方法。在測(cè)試代碼中我們已經(jīng)注釋了在update完account2以后奋渔,再次查詢的時(shí)候。account1會(huì)走緩存卒稳,而account2不會(huì)走緩存,而去查詢db充坑,觀察程序運(yùn)行日志,運(yùn)行日志為:
01:37:34.549 [main] INFO c.r.s.cache.example3.AccountService3 - real querying account... accountName1
01:37:34.551 [main] INFO c.r.s.cache.example3.AccountService3 - real querying db... accountName1
01:37:34.552 [main] INFO c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName1'}
01:37:34.553 [main] INFO c.r.s.cache.example3.AccountService3 - real querying account... accountName2
01:37:34.553 [main] INFO c.r.s.cache.example3.AccountService3 - real querying db... accountName2
01:37:34.555 [main] INFO c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName2'}
01:37:34.555 [main] INFO c.r.s.cache.example3.AccountService3 - real update db...accountName2
01:37:34.595 [main] INFO c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName1'}
01:37:34.596 [main] INFO c.r.s.cache.example3.AccountService3 - real querying account... accountName2
01:37:34.596 [main] INFO c.r.s.cache.example3.AccountService3 - real querying db... accountName2
01:37:34.596 [main] INFO c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName2'}
四捻爷、依照條件操作緩存
前面介紹的緩存方法,不論什么條件份企,都全部對(duì) accountService 對(duì)象的 getAccountByName 方法的調(diào)用都會(huì)啟動(dòng)緩存效果,無(wú)論參數(shù)是什么值。
設(shè)有一個(gè)需求甜紫,就是僅僅有賬號(hào)名稱的長(zhǎng)度小于等于 4 的情況下,才做緩存囚霸,大于 4 的不使用緩存
@CacheEvict源碼:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
String[] value() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
boolean allEntries() default false;
boolean beforeInvocation() default false;
}
通過查看CacheEvict注解的定義腰根,我們會(huì)發(fā)現(xiàn)定義中有一個(gè)condition描寫敘述:
Spring Expression Language (SpEL) attribute used for conditioning the method caching.Default is "", meaning the method is always cached.
我們能夠利用這種方法來(lái)完畢這個(gè)功能拓型,以下僅僅給出演示樣例代碼:
@Cacheable(value="accountCache",condition="#accountName.length() <= 4")// 緩存名叫 accountCache
public Account getAccountByName(String accountName) {
// 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯,直接實(shí)現(xiàn)業(yè)務(wù)
return getFromDB(accountName);
}
注意當(dāng)中的 condition=”#accountName.length() <=4”劣挫,這里使用了 SpEL 表達(dá)式訪問了參數(shù) accountName 對(duì)象的 length() 方法册养,條件表達(dá)式返回一個(gè)布爾值压固,true/false,當(dāng)條件為 true邓夕。則進(jìn)行緩存操作刘莹,否則直接調(diào)用方法運(yùn)行的返回結(jié)果焚刚。
五、假設(shè)有多個(gè)參數(shù)矿咕,怎樣進(jìn)行 key 的組合
我們要依據(jù)賬號(hào)名抢肛、password對(duì)賬號(hào)對(duì)象進(jìn)行緩存碳柱,而第三個(gè)參數(shù)“是否發(fā)送日志”對(duì)緩存沒有不論什么影響捡絮。所以莲镣,我們能夠利用 SpEL 表達(dá)式對(duì)緩存 key 進(jìn)行設(shè)計(jì)
我們?yōu)锳ccount類添加一個(gè)password 屬性, 然后改動(dòng)AccountService代碼:
@Cacheable(value="accountCache",key="#accountName.concat(#password)")
public Account getAccount(String accountName,String password,boolean sendLog) {
// 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯瑞侮。直接實(shí)現(xiàn)業(yè)務(wù)
return getFromDB(accountName,password);
}
注意上面的 key 屬性的圆,當(dāng)中引用了方法的兩個(gè)參數(shù) accountName 和 password半火,而 sendLog 屬性沒有考慮。由于其對(duì)緩存沒有影響钮糖。
accountService.getAccount("accountName", "123456", true);// 查詢數(shù)據(jù)庫(kù)
accountService.getAccount("accountName", "123456", true);// 走緩存
accountService.getAccount("accountName", "123456", false);// 走緩存
accountService.getAccount("accountName", "654321", true);// 查詢數(shù)據(jù)庫(kù)
accountService.getAccount("accountName", "654321", true);// 走緩存
六梅掠、怎樣做到:既要保證方法被調(diào)用。又希望結(jié)果被緩存
依據(jù)前面的樣例酪我,我們知道,假設(shè)使用了 @Cacheable 注解挠蛉,則當(dāng)反復(fù)使用同樣參數(shù)調(diào)用方法的時(shí)候,方法本身不會(huì)被調(diào)用運(yùn)行谴古。即方法本身被略過了质涛,取而代之的是方法的結(jié)果直接從緩存中找到并返回了掰担。
現(xiàn)實(shí)中并不總是如此,有些情況下我們希望方法一定會(huì)被調(diào)用带饱,由于其除了返回一個(gè)結(jié)果毡代,還做了其它事情勺疼。比如記錄日志教寂。調(diào)用接口等执庐。這個(gè)時(shí)候。我們能夠用 @CachePut 注解轨淌,這個(gè)注解能夠確保方法被運(yùn)行迂烁,同一時(shí)候方法的返回值也被記錄到緩存中递鹉。
@Cacheable(value="accountCache")
public Account getAccountByName(String accountName) {
// 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯,直接實(shí)現(xiàn)業(yè)務(wù)
return getFromDB(accountName);
}
// 更新 accountCache 緩存
@CachePut(value="accountCache",key="#account.getName()")
public Account updateAccount(Account account) {
return updateDB(account);
}
private Account updateDB(Account account) {
logger.info("real updating db..."+account.getName());
return account;
}
測(cè)試代碼例如以下:
Account account = accountService.getAccountByName("someone");
account.setPassword("123");
accountService.updateAccount(account);
account.setPassword("321");
accountService.updateAccount(account);
account = accountService.getAccountByName("someone");
logger.info(account.getPassword());
如上面的代碼所看到的躏结。我們首先用 getAccountByName 方法查詢一個(gè)人 someone 的賬號(hào)却盘。這個(gè)時(shí)候會(huì)查詢數(shù)據(jù)庫(kù)一次媳拴。可是也記錄到緩存中了禀挫。然后我們改動(dòng)了password旬陡,調(diào)用了 updateAccount 方法语婴。這個(gè)時(shí)候會(huì)運(yùn)行數(shù)據(jù)庫(kù)的更新操作且記錄到緩存,我們?cè)俅胃膭?dòng)password并調(diào)用 updateAccount 方法。然后通過 getAccountByName 方法查詢匿醒,這個(gè)時(shí)候。由于緩存中已經(jīng)有數(shù)據(jù)缠导,所以不會(huì)查詢數(shù)據(jù)庫(kù),而是直接返回最新的數(shù)據(jù)僻造,所以打印的password應(yīng)該是“321”
七憋他、@Cacheable髓削、@CachePut、@CacheEvict 凝視介紹
1.@Cacheable執(zhí)行緩存
@Cacheable可用于修飾類或修飾方法立膛,當(dāng)使用@Cacheable修飾類時(shí)揪罕,用于告訴Spring在類級(jí)別上進(jìn)行緩存———程序調(diào)用該類的實(shí)例的任何方法時(shí)都需要緩存宝泵,而且共享同一個(gè)緩存區(qū);當(dāng)使用@Cacheable修飾方法時(shí)儿奶,用于告訴Spring在方法級(jí)別上進(jìn)行緩存———只有當(dāng)程序調(diào)用該方法時(shí)才需要緩存
類級(jí)別的緩存:
使用@Cacheable修飾類時(shí)框往,就可控制Spring在類級(jí)別進(jìn)行緩存廓握,這樣當(dāng)程序調(diào)用該類的任意方法時(shí),只要傳入的參數(shù)相同隙券,Spring就會(huì)使用緩存
@Service("userService")
// 指定將數(shù)據(jù)放入users緩存區(qū)
@Cacheable(value = "users")
public class UserServiceImpl implements UserService
{
public User getUsersByNameAndAge(String name, int age)
{
System.out.println("--正在執(zhí)行findUsersByNameAndAge()查詢方法--");
return new User(name, age);
}
public User getAnotherUser(String name, int age)
{
System.out.println("--正在執(zhí)行findAnotherUser()查詢方法--");
return new User(name, age);
}
}
當(dāng)程序第一次調(diào)用該類的實(shí)例的某個(gè)方法時(shí)男应,Spring緩存機(jī)制會(huì)將該方法返回的數(shù)據(jù)放入指定緩存區(qū)娱仔。以后程序調(diào)用該類的實(shí)例的任何方法時(shí),只要傳入的參數(shù)相同牲迫,Spring將不會(huì)真正執(zhí)行該方法耐朴,而是直接利用緩存區(qū)中的數(shù)據(jù)
使用@Cacheable是可指定如下屬性:
- value:必須屬性盹憎。該屬性可指定多個(gè)緩存區(qū)的名字,用于指定將方法返回值放入指定的緩存區(qū)內(nèi)
- key:通過SpEL表達(dá)式顯式指定緩存的key
- condition:該屬性指定一個(gè)返回boolean值的SpEL表達(dá)式陪每,只有當(dāng)該表達(dá)式返回true時(shí)影晓,Spring才會(huì)緩存方法返回值
- unless:該屬性指定一個(gè)返回boolean值的SpEL表達(dá)式,當(dāng)該表達(dá)式返回true時(shí)挂签,Spring就不緩存方法返回值
與@Cache注解功能類似的還有一個(gè)@Cacheput注解疤祭,與@Cacheable不同的是饵婆,@Cacheput修飾的方法不會(huì)讀取緩存區(qū)中的數(shù)據(jù)———這意味著不管緩存區(qū)是否已有數(shù)據(jù),@Cacheput總會(huì)告訴Spring要重新執(zhí)行這些方法侨核,并在此將方法返回值放入緩存區(qū)
方法級(jí)別的緩存:
使用@Cacheable修飾方法時(shí)草穆,就可控制Spring在方法級(jí)別進(jìn)行緩存搓译,這樣當(dāng)程序調(diào)用該方法時(shí)续挟,只要傳入的參數(shù)相同侥衬,Spring就會(huì)使用緩存
@Service("userService")
public class UserServiceImpl implements UserService
{
@Cacheable(value = "users1")
public User getUsersByNameAndAge(String name, int age)
{
System.out.println("--正在執(zhí)行findUsersByNameAndAge()查詢方法--");
return new User(name, age);
}
@Cacheable(value = "users2")
public User getAnotherUser(String name, int age)
{
System.out.println("--正在執(zhí)行findAnotherUser()查詢方法--");
return new User(name, age);
}
}
上面代碼中指定getUsersByNameAndAge()和getAnotherUser()方法分別使用不同的緩存區(qū),這意味著兩個(gè)方法都會(huì)緩存轴总,但由于它們使用了不同的緩存區(qū)直颅,因此它們不能共享緩存區(qū)數(shù)據(jù)
2.使用@CacheEvict清除緩存
被@CacheEvict注解修飾的方法可用于清除緩存怀樟,使用@CacheEvict注解時(shí)可指定如下屬性:
- value:必需屬性。用于指定該方法用于清除哪個(gè)緩存區(qū)的數(shù)據(jù)
- key:通過SpEL表達(dá)式顯式指定緩存的key
- allEntries:該屬性指定是否清空整個(gè)緩存區(qū)
- beforeInvocation:該屬性指定是否在執(zhí)行方法之前清除緩存往堡。默認(rèn)實(shí)在false
- condion:該屬性指定一個(gè)SpEL變道時(shí)械荷,只有當(dāng)該表達(dá)式為true時(shí)才清除緩存
@Service("userService")
@Cacheable(value = "users")
public class UserServiceImpl implements UserService
{
public User getUsersByNameAndAge(String name, int age)
{
System.out.println("--正在執(zhí)行findUsersByNameAndAge()查詢方法--");
return new User(name, age);
}
public User getAnotherUser(String name, int age)
{
System.out.println("--正在執(zhí)行findAnotherUser()查詢方法--");
return new User(name, age);
}
// 指定根據(jù)name虑灰、age參數(shù)清除緩存
@CacheEvict(value = "users")
public void evictUser(String name, int age)
{
System.out.println("--正在清空"+ name
+ " , " + age + "對(duì)應(yīng)的緩存--");
}
// 指定清除user緩存區(qū)所有緩存數(shù)據(jù)
@CacheEvict(value = "users" , allEntries=true)
public void evictAll()
{
System.out.println("--正在清空整個(gè)緩存--");
}
}
3.基本原理
一句話介紹就是Spring AOP的動(dòng)態(tài)代理技術(shù)。