首頁 > 軟體

Spring Cache快取介紹

2020-06-16 17:33:38

Spring 3.1 引入了激動人心的基於注釋(annotation)的快取(cache)技術,它本質上不是一個具體的快取實現方案(例如 EHCache 或者 OSCache),而是一個對快取使用的抽象,通過在既有程式碼中新增少量它定義的各種 annotation,即能夠達到快取方法的返回物件的效果。

Spring 的快取技術還具備相當的靈活性,不僅能夠使用 SpEL(Spring Expression Language)來定義快取的 key 和各種 condition,還提供開箱即用的快取臨時儲存方案,也支援和主流的專業快取例如 EHCache 整合。

其特點總結如下:

1.通過少量的設定 annotation 注釋即可使得既有程式碼支援快取

2.支援開箱即用 Out-Of-The-Box,即不用安裝和部署額外第三方元件即可使用快取

3.支援 Spring Express Language,能使用物件的任何屬性或者方法來定義快取的 key 和 condition

4.支援 AspectJ,並通過其實現任何方法的快取支援

5.支援自定義 key 和自定義快取管理者,具有相當的靈活性和擴充套件性

一、基於註解的支援

Spring為我們提供了幾個註解來支援Spring Cache。其核心主要是@Cacheable、@CachePut 和@CacheEvict。使用@Cacheable標記的方法在執行後Spring Cache將快取其返回結果,@CachePut主要針對方法設定,能夠根據方法的請求引數對其結果進行快取,和 @Cacheable不同的是,它每次都會觸發真實方法的呼叫,而使用@CacheEvict標記的方法會在方法執行前或者執行後移除Spring Cache中的某些元素。

1.@Cacheable

@Cacheable可以標記在一個方法上,也可以標記在一個類上。當標記在一個方法上時表示該方法是支援快取的,當標記在一個類上時則表示該類所有的方法都是支援快取的。對於一個支援快取的方法,Spring會在其被呼叫後將其返回值快取起來,以保證下次利用同樣的引數來執行該方法時可以直接從快取中獲取結果,而不需要再次執行該方法。Spring在快取方法的返回值時是以鍵值對進行快取的,值就是方法的返回結果,至於鍵的話,Spring又支援兩種策略,預設策略和自定義策略,需要注意的是當一個支援快取的方法在物件內部被呼叫時是不會觸發快取功能的。@Cacheable可以指定三個屬性,value、key和condition。

value:快取的名稱,在 spring 組態檔中定義,必須指定至少一個。如@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}

key:快取的 key,可以為空,如果指定要按照 SpEL 表示式編寫,如果不指定,則預設按照方法的所有引數進行組合。如@Cacheable(value=”testcache”,key=”#userName”)

condition:快取的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行快取。如@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

註:除了上述使用方法引數作為key之外,Spring還為我們提供了一個root物件可以用來生成key。通過該root物件我們可以獲取到以下資訊。

屬性名稱

描述

範例

methodName

當前方法名

#root.methodName

method

當前方法

#root.method.name

target

當前被呼叫的物件

#root.target

targetClass

當前被呼叫的物件的class

#root.targetClass

args

當前方法引數組成的陣列

#root.args[0]

caches

當前被呼叫的方法使用的Cache

#root.caches[0].name

2.@CachePut
在支援Spring Cache的環境下,對於使用@Cacheable標註的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的快取元素,如果存在就不再執行該方法,而是直接從快取中獲取結果進行返回,否則才會執行並將返回結果存入指定的快取中。@CachePut也可以宣告一個方法支援快取功能。與@Cacheable不同的是使用@CachePut標註的方法在執行前不會去檢查快取中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的快取中。
@CachePut也可以標注在類上和方法上。使用@CachePut時我們可以指定的屬性跟@Cacheable是一樣的。
3.@CacheEvict
@CacheEvict是用來標注在需要清除快取元素的方法或類上的。當標記在一個類上時表示其中所有的方法的執行都會觸發快取的清除操作。@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的語意與@Cacheable對應的屬性類似。即value表示清除操作是發生在哪些Cache上的(對應Cache的名稱);key表示需要清除的是哪個key,如未指定則會使用預設策略生成的key;condition表示清除操作發生的條件。下面我們來介紹一下新出現的兩個屬性allEntries和beforeInvocation。
allEntries:是否清空所有快取內容,預設為 false,如果指定為 true,則方法呼叫後將立即清空所有快取。如:@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation:是否在方法執行前就清空,預設為 false,如果指定為 true,則在方法還沒有執行的時候就清空快取,預設情況下,如果方法執行丟擲異常,則不會清空快取。如:@CachEvict(value=”testcache”,beforeInvocation=true)
其他引數和@Cacheable相同
4.@Caching
@Caching註解可以讓我們在一個方法或者類上同時指定多個Spring Cache相關的註解。其擁有三個屬性:cacheable、put和evict,分別用於指定@Cacheable、@CachePut和@CacheEvict。如: @Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),@CacheEvict(value = "cache3", allEntries = true) })


二、範例

使用map集合實現快取管理,演示spring cache的使用。

1.建立快取物件範例


package org.springframework.cache.demo;

import java.io.Serializable;

//快取物件
public class User implements Serializable{
 /**
  *
  */
 private static final long serialVersionUID = 1L;
 private int id;
 private String name;
 
 public User(){
 }
 
 public User(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;
 }
}
2.物件服務實現類


package org.springframework.cache.demo;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;

/**
 * 業務服務
 *
 */
public class UserService {

 @Cacheable(value = "userCache",key="#userName")
 // 使用了一個快取名叫 userCache
 public User getUserByName(String userName) {
  // 方法內部實現不考慮快取邏輯,直接實現業務
  return getFromDB(userName);
 }

 @CacheEvict(value = "userCache", key = "#user.name")
 // 清空 accountCache 快取
 public void updateUser(User user) {
  updateDB(user);
 }

 @CacheEvict(value = "userCache", allEntries = true,beforeInvocation=true)
 // 清空 accountCache 快取
 public void reload() {
 }

 private User getFromDB(String userName) {
  System.out.println("查詢資料庫..." + userName);
  return new User(userName);
 }

 private void updateDB(User user) {
  System.out.println("更新資料庫資料..." + user.getName());
 }
}
3.快取實現


package org.springframework.cache.demo.mycache;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;

public class MyCache implements Cache {

 private String name;
 private Map<String, Object> store = new ConcurrentHashMap<String, Object>();;

 public MyCache() {
 }

 public MyCache(String name) {
  this.name = name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public void clear() {
  store.clear();
 }

 public void evict(Object obj) {
 }

 public ValueWrapper get(Object key) {
  ValueWrapper result = null;
  Object thevalue = store.get(key);
  if (thevalue != null) {
   result = new SimpleValueWrapper(thevalue);
  }
  return result;
 }

 public <T> T get(Object key, Class<T> clazz) {
  return clazz.cast(store.get(key));
 }

 public String getName() {
  return name;
 }

 public Object getNativeCache() {
  return store;
 }

 public void put(Object key, Object value) {
  store.put((String) key, value);
 }

 public ValueWrapper putIfAbsent(Object key, Object value) {
  put(key, value);
  return new SimpleValueWrapper(value);
 }
}
4.spring設定

<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">
 <!-- 啟用快取註解功能,這個是必須的,否則註解不會生效,另外,該註解一定要宣告在spring主組態檔中才會生效 -->
 <cache:annotation-driven cache-manager="cacheManager" />

 <bean id="userService" class="org.springframework.cache.demo.UserService" />

 <!-- generic cache manager -->
 <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
  <property name="caches">
   <set>
    <bean class="org.springframework.cache.demo.mycache.MyCache"
     p:name="userCache" />
   </set>
  </property>
 </bean>
</beans>5.執行類


package org.springframework.cache.demo.mycache;

import org.springframework.cache.demo.User;
import org.springframework.cache.demo.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyMain {

 @SuppressWarnings("resource")
 public static void main(String[] args) {
  ApplicationContext context = new ClassPathXmlApplicationContext("spring-cache-mycache.xml");
  UserService userService = context.getBean(UserService.class);
  // 第一次查詢,應該走資料庫
  System.out.print("第一次查詢...");
  userService.getUserByName("hello");
  // 第二次查詢,應該不查資料庫,直接返回快取的值
  System.out.println("第二次查詢...");
  userService.getUserByName("hello");
  System.out.println();
  System.out.println("==============");

  // 更新某個記錄的快取,首先構造兩個使用者記錄,然後記錄到快取中
  User user1 = userService.getUserByName("user1");
  // 開始更新其中一個
  user1.setId(1000);
  userService.updateUser(user1);
  // 因為被更新了,所以會查詢資料庫
  userService.getUserByName("user1");
  // 再次查詢,應該走快取
  userService.getUserByName("user1");
  // 更新所有快取
  userService.reload();
  System.out.println("清楚所有快取");
  // 查詢資料庫
  userService.getUserByName("user1");
  userService.getUserByName("user2");
  // 查詢快取
  userService.getUserByName("user1");
  userService.getUserByName("user2");
 }
}
執行結果:


第一次查詢...查詢資料庫...hello
第二次查詢...

==============
查詢資料庫...user1
更新資料庫資料...user1
清楚所有快取
查詢資料庫...user1
查詢資料庫...user2

本文永久更新連結地址http://www.linuxidc.com/Linux/2016-09/135128.htm


IT145.com E-mail:sddin#qq.com