<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Cache 緩存原理與 Redis 實(shí)踐

          共 14928字,需瀏覽 30分鐘

           ·

          2022-08-01 09:35


          說到Spring Boot緩存,那就不得不提JSR-107規(guī)范,它告訴我們?cè)贘ava中如何規(guī)范地使用緩存。

          JSR是Java Specification Requests的簡(jiǎn)稱,通常譯為”Java 規(guī)范提案“。具體而言,是指向JCP(Java Community Process,Java標(biāo)準(zhǔn)制定組織)提出新增一個(gè)標(biāo)準(zhǔn)化技術(shù)規(guī)范的正式請(qǐng)求。任何人都可以提交JSR,通過一定的標(biāo)準(zhǔn)測(cè)試后,就可以向Java平臺(tái)增添新的API和服務(wù)。JSR已成為Java界的一個(gè)重要標(biāo)準(zhǔn)。

          JSR-107規(guī)范即JCache API,JCache規(guī)范定義了一種對(duì)Java對(duì)象臨時(shí)在內(nèi)存中進(jìn)行緩存的方法,包括對(duì)象的創(chuàng)建、共享訪問、假脫機(jī)(spooling)、失效、各JVM的一致性等,可被用于緩存JSP內(nèi)最經(jīng)常讀取的數(shù)據(jù),如產(chǎn)品目錄和價(jià)格列表。利用JCache的緩存數(shù)據(jù),可以加快大多數(shù)查詢的反應(yīng)時(shí)間(內(nèi)部測(cè)試表明反應(yīng)時(shí)間大約快15倍)。

          一、JCache

          在Spring Boot中使用緩存之前,我們有必要了解一下JCache。JCache定義了五個(gè)核心接口,分別是CachingProvider,CacheManager,Cache,Entry和Expiry。

          • 一個(gè)CachingProvider可以創(chuàng)建和管理多個(gè)CacheManager,并且一個(gè)CacheManager只能被一個(gè)CachingProvider所擁有,而一個(gè)應(yīng)用可以在運(yùn)行期間訪問多個(gè)CachingProvider。

          • 一個(gè)CacheManager可以創(chuàng)建和管理多個(gè)唯一命名的Cache,且一個(gè)Cache只能被一個(gè)CacheManager所擁有,這些Cache存在于CacheManager的上下文中。

          • Cache是一個(gè)類似Map的數(shù)據(jù)結(jié)構(gòu)

          • Entry是一個(gè)存儲(chǔ)在Cache中的key-value對(duì)

          • Expiry是指存儲(chǔ)在Cache中的Entry的有效期,一旦超過這個(gè)時(shí)間,Entry將處于過期狀態(tài),即不可訪問、更新和刪除。緩存有效期可以通過ExpiryPolicy設(shè)置。



          二、Spring Cache原理

          Spring 3.1開始,引入了Spring Cache,即Spring 緩存抽象。通過定義org.springframework.cache.Cache和org.springframework.cache.CacheManager接口來統(tǒng)一不同的緩存技術(shù),并支持使用JCache注解簡(jiǎn)化開發(fā)過程。

          Cache接口為緩存的組件規(guī)范定義,包含緩存的各種操作集合。Spring中為Cache接口提供了各種xxxCache的實(shí)現(xiàn):RedisCache,EhCacheCache,ConcurrentMapCache等。
          我們通過部分源碼詳細(xì)了解一下Cache接口和CacheManager接口。

          Cache接口

           public interface Cache {      //Cache名稱    String getName();        //Cache負(fù)責(zé)緩存的對(duì)象    Object getNativeCache();
          /** * 獲取key對(duì)應(yīng)的ValueWrapper * 沒有對(duì)應(yīng)的key,則返回null * key對(duì)應(yīng)的value是null,則返回null對(duì)應(yīng)的ValueWrapper */ @Nullable Cache.ValueWrapper get(Object key);
          //返回key對(duì)應(yīng)type類型的value @Nullable <T> T get(Object key, @Nullable Class<T> type);
          //返回key對(duì)應(yīng)的value,沒有則緩存Callable::call,并返回 @Nullable <T> T get(Object key, Callable<T> valueLoader);
          //緩存目標(biāo)key-value(替換舊值),不保證實(shí)時(shí)性 void put(Object key, @Nullable Object value);
          //插入緩存,并返回該key對(duì)應(yīng)的value;先調(diào)用get,不存在則用put實(shí)現(xiàn) @Nullable default Cache.ValueWrapper putIfAbsent(Object key, @Nullable Object value) { Cache.ValueWrapper existingValue = this.get(key); if (existingValue == null) { this.put(key, value); }
          return existingValue; } //刪除緩存,不保證實(shí)時(shí)性 void evict(Object key);
          //立即刪除緩存:返回false表示剔除前不存在制定key活不確定是否存在;返回true,表示該key之前存在 default boolean evictIfPresent(Object key) { this.evict(key); return false; } //清除所有緩存,不保證實(shí)時(shí)性 void clear();
          //立即清楚所有緩存,返回false表示清除前沒有緩存或不能確定是否有;返回true表示清除前有緩存 default boolean invalidate() { this.clear(); return false; } public static class ValueRetrievalException extends RuntimeException { @Nullable private final Object key;
          public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) { super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex); this.key = key; }
          @Nullable public Object getKey() { return this.key; } }
          //緩存值的一個(gè)包裝器接口,實(shí)現(xiàn)類為SimpleValueWrapper @FunctionalInterface public interface ValueWrapper { @Nullable Object get(); }}

          可以看出,Cache接口抽象了緩存的 get put evict 等相關(guān)操作。

          AbstractValueAdaptingCache

          public abstract class AbstractValueAdaptingCache implements Cache {    //是否允許Null值    private final boolean allowNullValues;
          protected AbstractValueAdaptingCache(boolean allowNullValues) { this.allowNullValues = allowNullValues; }
          public final boolean isAllowNullValues() { return this.allowNullValues; }
          @Nullable public ValueWrapper get(Object key) { return this.toValueWrapper(this.lookup(key)); }
          @Nullable public <T> T get(Object key, @Nullable Class<T> type) { //查詢到的緩存值做fromStoreValue轉(zhuǎn)換 Object value = this.fromStoreValue(this.lookup(key)); //轉(zhuǎn)換后非null值且無法轉(zhuǎn)換為type類型則拋出異常 if (value != null && type != null && !type.isInstance(value)) { throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value); } else { return value; } }
          //從緩存中獲取key對(duì)應(yīng)的value,子類實(shí)現(xiàn) @Nullable protected abstract Object lookup(Object key);
          //對(duì)于從緩存中獲取的值,允許為空且值為NullValue時(shí),處理為null @Nullable protected Object fromStoreValue(@Nullable Object storeValue) { return this.allowNullValues && storeValue == NullValue.INSTANCE ? null : storeValue; }
          //對(duì)于要插入緩存的null值,在允許null值的情況下處理為NullValue;否則拋出異常IllegalArgumentException protected Object toStoreValue(@Nullable Object userValue) { if (userValue == null) { if (this.allowNullValues) { return NullValue.INSTANCE; } else { throw new IllegalArgumentException("Cache '" + this.getName() + "' is configured to not allow null values but null was provided"); } } else { return userValue; } }
          //get操作依據(jù),查詢到緩存值非null,則fromStoreValue轉(zhuǎn)換后包裝成SimpleValueWrapper返回 @Nullable protected ValueWrapper toValueWrapper(@Nullable Object storeValue) { return storeValue != null ? new SimpleValueWrapper(this.fromStoreValue(storeValue)) : null; }}

          抽象類AbstractValueAdaptingCache實(shí)現(xiàn)了Cache接口,主要抽象了對(duì)NULL值的處理邏輯。

          • allowNullValues屬性表示是否允許處理NULL值的緩存

          • fromStoreValue方法處理NULL值的get操作,在屬性allowNullValues為true的情況下,將NullValue處理為NULL

          • toStoreValue方法處理NULL值得put操作,在屬性allowNullValues為true的情況下,將NULL處理為NullValue,否則拋出異常

          • toValueWrapper方法提供Cache接口中g(shù)et方法的默認(rèn)實(shí)現(xiàn),從緩存中讀取值,再通過fromStoreValue轉(zhuǎn)化,最后包裝為SimpleValueWrapper返回

          • ValueWrapper get(Object key)T get(Object key, @Nullable Class<T> type)方法基于上述方法實(shí)現(xiàn)

          • ValueWrapper get(Object key)@Nullable Class<T> type)方法基于上述方法實(shí)現(xiàn)

          • lookup抽象方法用于給子類獲取真正的緩存值

          ConcurrentMapCache


          public class ConcurrentMapCache extends AbstractValueAdaptingCache {    private final String name;    //定義ConcurrentMap緩存    private final ConcurrentMap<Object, Object> store;    //如果要緩存的是值對(duì)象的copy,則由此序列化代理類處理    @Nullable    private final SerializationDelegate serialization;
          public ConcurrentMapCache(String name) { this(name, new ConcurrentHashMap(256), true); }
          //默認(rèn)允許處理null public ConcurrentMapCache(String name, boolean allowNullValues) { this(name, new ConcurrentHashMap(256), allowNullValues); }
          //默認(rèn)serialization = null public ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues) { this(name, store, allowNullValues, (SerializationDelegate)null); }
          protected ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues, @Nullable SerializationDelegate serialization) { super(allowNullValues); Assert.notNull(name, "Name must not be null"); Assert.notNull(store, "Store must not be null"); this.name = name; this.store = store; this.serialization = serialization; }
          //serialization不為空,緩存值對(duì)象的copy public final boolean isStoreByValue() { return this.serialization != null; }
          public final String getName() { return this.name; }
          public final ConcurrentMap<Object, Object> getNativeCache() { return this.store; }
          //實(shí)現(xiàn)lookup:store#get @Nullable protected Object lookup(Object key) { return this.store.get(key); }
          //基于ConcurrentMap::computeIfAbsent方法實(shí)現(xiàn);get和put的值由fromStoreValue和toStoreValue處理Null @Nullable public <T> T get(Object key, Callable<T> valueLoader) { return this.fromStoreValue(this.store.computeIfAbsent(key, (k) -> { try { return this.toStoreValue(valueLoader.call()); } catch (Throwable var5) { throw new ValueRetrievalException(key, valueLoader, var5); } })); }
          public void put(Object key, @Nullable Object value) { this.store.put(key, this.toStoreValue(value)); }
          @Nullable public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { Object existing = this.store.putIfAbsent(key, this.toStoreValue(value)); return this.toValueWrapper(existing); }
          public void evict(Object key) { this.store.remove(key); }
          public boolean evictIfPresent(Object key) { return this.store.remove(key) != null; }
          public void clear() { this.store.clear(); }
          public boolean invalidate() { boolean notEmpty = !this.store.isEmpty(); this.store.clear(); return notEmpty; }
          protected Object toStoreValue(@Nullable Object userValue) { Object storeValue = super.toStoreValue(userValue); if (this.serialization != null) { try { return this.serialization.serializeToByteArray(storeValue); } catch (Throwable var4) { throw new IllegalArgumentException("Failed to serialize cache value '" + userValue + "'. Does it implement Serializable?", var4); } } else { return storeValue; } }
          protected Object fromStoreValue(@Nullable Object storeValue) { if (storeValue != null && this.serialization != null) { try { return super.fromStoreValue(this.serialization.deserializeFromByteArray((byte[])((byte[])storeValue))); } catch (Throwable var3) { throw new IllegalArgumentException("Failed to deserialize cache value '" + storeValue + "'", var3); } } else { return super.fromStoreValue(storeValue); } }}

          ConcurrentMapCache繼承了抽象類AbstractValueAdaptingCache,是Spring的默認(rèn)緩存實(shí)現(xiàn)。它支持對(duì)緩存對(duì)象copy的緩存,由SerializationDelegate serialization 處理序列化,默認(rèn)為 null 即基于引用的緩存。緩存相關(guān)操作基于基類 AbstractValueAdaptingCache 的 null 值處理,默認(rèn)允許為 null。

          CacheManager

          public interface CacheManager {    @Nullable    //獲取指定name的Cache,可能延遲創(chuàng)建    Cache getCache(String name);
          //獲取當(dāng)前CacheManager下的Cache name集合
          Collection<String> getCacheNames();}

          CacheManager 基于 name 管理一組 Cache。當(dāng)然,CacheManager也有很多實(shí)現(xiàn)類,如ConcurrentMapCacheManager、AbstractCacheManager及SimpleCacheManager,這些xxxCacheManager類都是為了制定Cache的管理規(guī)則,這里就不再深入探討了。

          三、Spring Cache實(shí)踐

          除了第二章中提到的Cache接口和CacheManager接口,在使用Spring 緩存抽象時(shí),我們還會(huì)用到一些JCache注解。

          Spring Cache中一些概念和注解

          @Cacheable、@CacheEvict和@CachePut三個(gè)注解都是對(duì)方法進(jìn)行配置,主要參數(shù)如下圖所示:

          Cache中可用的SpEL表達(dá)式如下圖所示:

          @EnableCaching

          這個(gè)注解表示開啟基于注解的緩存,一般放在主程序類前面,如下所示:

          @EnableCachingpublic class SpringBootCacheApplication {        public static void main(String[] args) {        SpringApplication.run(SpringBootCacheApplication.class, args);    }}

          @Cacheable

          這個(gè)注解放在方法前,可以將方法的運(yùn)行結(jié)果進(jìn)行緩存,之后就不用調(diào)用方法了,直接從緩存中取值即可。常用屬性可以見上圖。以下是常見用法:

          @Cacheable(cacheNames = {"emp"}, key = "#id", conditon = "mid>0", unless = "#result == null")public Employee getEmp(Integer id) {    Employee emp = employeeMapper.getEmpById(id);return emp;    }}

          @Cacheable注解中常用的參數(shù)有cacheNames/value(指定緩存組件的名字,可以指定多個(gè))、key(緩存數(shù)據(jù)時(shí)使用的key,默認(rèn)使用方法參數(shù)的值,也可以自定義)、keyGenerator(key的生成器,可以自定義,key與keyGenerator二選一)、cacheManager(指定緩存管理器,或者使用cacheResolver指定獲取解析器)、condition(符合條件才緩存)、unless(符合條件則不緩存,可以獲取方法運(yùn)行結(jié)果進(jìn)行判斷)、sync(是否使用異步模式,不可與unless一起使用)。@Cacheable的運(yùn)行原理:

          1. 方法運(yùn)行前,程序會(huì)使用cacheManager根據(jù)cacheNames獲取Cache,如果沒有對(duì)應(yīng)名稱的Cache,則自動(dòng)創(chuàng)建一個(gè)Cache。

          2. 使用key去Cache中查找對(duì)應(yīng)的緩存內(nèi)容。key默認(rèn)使用SimpleKeyGenerator生成,其生成策略如下:

          • 如果沒有參數(shù),key=new SimpleKey()

          • 如果只有一個(gè)參數(shù),key=參數(shù)值

          • 如果有多個(gè)參數(shù),key=new SimpleKey(params)

          1. 沒有查到緩存值,則調(diào)用目標(biāo)方法

          2. 以步驟二中返回的key,目標(biāo)方法返回的結(jié)果為value,存入緩存

          @CachePut

          @CachePut注解先調(diào)用目標(biāo)方法,然后再緩存目標(biāo)方法的結(jié)果。

          @CachePut(value = "emp", key = "#result.id")public Employee updateEmp(Employee employee) {   employeeMapper.updateEmp(employee);return employee;}

          @CacheEvict

          @CacheEvict用于清除緩存,可以通過key指定需要清除的緩存,allEntries置為true表示刪除所有緩存。

          @CacheEvict(value = "emp", key = "#id", allEntries = true)public void deleteEmp(Integer id) {  employeeMapper.deleteEmpById(id);}

          默認(rèn)刪除緩存行為在方法執(zhí)行之后(beforeInvocation=false),如果方法運(yùn)行異常,則該緩存不會(huì)被刪除。但可以通過設(shè)置beforeInvocation = true,將刪除緩存行為在方法執(zhí)行之前。

          @Caching&@CacheConfig

          @Caching注解中包含了@Cacheable、@CachePut和@CacheEvict注解,可以同時(shí)指定多個(gè)緩存規(guī)則。示例如下所示:

          @Caching(cacheable = {    @Cacheable(value = "emp", key = "#lastName")}, put = {    @CachePut(value = "emp", key = "#result.id")    @CachePut(value = "emp", key = "#result.email") })

          @CacheConfig注解放在類上,抽取緩存的公共配置,如cacheNames、cacheManager等,這樣就不用在每個(gè)緩存注解中重復(fù)配置了。

          四、Redis測(cè)試

          Spring Boot里面默認(rèn)使用的Cache和CacheManager分別是ConcurrentMapCache和ConcurrentMapCacheManager,將數(shù)據(jù)存儲(chǔ)在ConcurrentMap<Object,Object>中。
          然而,在實(shí)際開發(fā)過程中,一般會(huì)使用一些緩存中間件,如Redis、Memcached和Encache等。接下來,演示一下Redis環(huán)境搭建與測(cè)試。

          Redis環(huán)境搭建

          Redis是一個(gè)開源的、內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng),可以作為數(shù)據(jù)庫、緩存和消息中間件。這里選擇用Docker搭建Redis環(huán)境。首先需要下載鏡像,然后啟動(dòng),具體命令如下:

          // 默認(rèn)拉取最新的Redis鏡像docker pull redis// 啟動(dòng)Redis容器docker run -d -p 6379:6379 --name myredis redis

          接下來,我們使用Redis Desktop Manager軟件連接Redis,Redis的端口號(hào)默認(rèn)為6379。

          RedisAutoConfiguration.java文件里面定義了StringRedisTemplate(操作字符串)和RedisTemplate組件,將組件自動(dòng)注入代碼中,即可使用。兩個(gè)組件都有針對(duì)Redis不同數(shù)據(jù)類型的處理方法。Redis常見的五大數(shù)據(jù)類型:
          String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)

          stringRedisTemplate.opsForValue()[字符串]stringRedisTemplate.opsForList()[列表]stringRedisTemplate.opsForSet()[集合]stringRedisTemplate.opsForHash()[散列]stringRedisTemplate.opsForZSet()[有序集合]

          下面是使用示例:

          public class RedisTest {    @Autowired    StringRedisTemplate stringRedisTemplate;
          @Autowired RedisTemplate redisTemplate;
          // 測(cè)試保存數(shù)據(jù) public void test01 { stringRedisTemplate.opsForValue().append("msg","hello");//存入數(shù)據(jù) String s = stringRedisTemplate.opsForValue().get("msg");//獲取數(shù)據(jù) stringRedisTemplate.opsForList().leftPush("mylist","1"); }
          //測(cè)試保存對(duì)象 public void test02 { Employee emp = new Employee(); //默認(rèn)使用jdk序列化機(jī)制,將對(duì)象序列化后的數(shù)據(jù)保存至Redis中 redisTemplate.opsForValue().set("emp-01", empById); }}

          如果想將對(duì)象以json的方式保存,可將對(duì)象轉(zhuǎn)換為json或改變RedisTemplate中的默認(rèn)序列化規(guī)則。后者的參考代碼如下,首先找到Redis的自動(dòng)加載類RedisAutoConfiguration,自定義一個(gè)RedisTemplate,然后放入容器中。

          public class MyRedisConfig {    @Bean    public RedisTemplate<Object, Employee>      empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();        template.setConnectionFactory(redisConnectionFactory);        Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);        template.setDefaultSerizlizer(ser);        return template;
          }}

          自定義CacheManager

          當(dāng)Spring Boot項(xiàng)目中引入Redis的starter依賴時(shí),會(huì)將RedisCacheManager作為默認(rèn)的CacheManager。RedisCacheManager管理RedisCache,后者使用RedisTemplate<Object,Object>操作Redis,默認(rèn)序列化機(jī)制是jdk。如果需要改變Redis序列化機(jī)制,可以自定義CacheManager。參考代碼如下:

          @Beanpublic RedisCacheManager employeeCacheManager(RedisTemplate<Object,Object> employeeRedisTemplate) {    RedisCacheManager cacheManager = new RedisCacheManager(employeeRedisTemplate);    // 將cacheNames作為key的前綴    cacheManager.setUserPrefix(true);    return cacheManager;}

          自定義RedisCache和CacheManager都可以更改緩存方式,兩者區(qū)別在于:前者用于自定義緩存層,后者將緩存交給spring管理(spring cache中不止Redis)。

          作者:lynnnnyl鏈接:https://www.jianshu.com/p/0865248ddd74

          記得點(diǎn)「」和「在看」↓

          愛你們

          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99re在线视频 | 人人爱天天摸 | www.日韩AV | 国产一区俺也去 | 特级西西444www大胆高清图片 |