1. 程式人生 > 其它 >淺析SpringBoot快取原理探究、SpringCache常用註解介紹及如何整合Redis

淺析SpringBoot快取原理探究、SpringCache常用註解介紹及如何整合Redis

一、SpringBoot 快取原理探究

1、SpringCache 介紹

  在SpringBoot中,資料的快取管理儲存依賴於Spring框架中cache相關的org.springframework.cache.Cache和org.springframework.cache.CacheManager快取管理器介面。

  如果程式中沒有定義型別為CacheManager的Bean元件或者是名為cacheResolver的CacheResolver快取解析器,Spring Boot將嘗試選擇並啟用以下快取元件(按照指定的順序):

(1)Generic
(2)JCache (JSR-107)(EhCache 3
、Hazelcast、Infinispan等) (3)EhCache 2.x (4)Hazelcast (5)Infinispan (6)Couchbase (7)Redis (8)Caffeine (9)Simple (10)NoOp

  上面按照Spring Boot快取元件的載入順序,列舉了支援的10種快取元件,在專案中新增某個快取管理元件(例如Redis)後,Spring Boot專案會選擇並啟用對應的快取管理器。

  如果專案中同時添加了多個快取元件,且沒有指定快取管理器或者快取解析器(CacheManager或者cacheResolver),那麼Spring Boot會按照上述順序在新增的多個快取中優先啟用指定的快取元件進行快取管理。

  Spring Boot預設快取管理中,沒有新增任何快取管理元件能實現快取管理。這是因為開啟快取管理後,Spring Boot會按照上述列表順序查詢有效的快取元件進行快取管理,如果沒有任何快取元件,會預設使用Simple快取元件進行管理。

  Simple快取元件是Spring Boot預設的快取管理元件,它預設使用記憶體中的ConcurrentMap進行快取儲存,所以在沒有新增任何第三方快取元件的情況下,可以實現記憶體中的快取管理,但是我們不推薦使用這種快取管理方式。

  當在Spring Boot預設快取管理的基礎上引入Redis快取元件,即在pom.xml檔案中新增Spring Data Redis依賴啟動器後,SpringBoot會使用RedisCacheConfigratioin當做生效的自動配置類進行快取相關的自動裝配,容器中使用的快取管理器是RedisCacheManager, 這個快取管理器建立的Cache為 RedisCache, 進而操控redis進行資料的快取。

  依賴:org.springframework.boot   和   spring-boot-starter-data-redis

2、快取配置類:SpringBoot提供了10個配置類,對應上面介紹的那 10 個快取元件,其中SimpleCacheConfiguration是預設配置類

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.SimpleCacheConfiguration  // 預設
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

3、SpringBoot各類快取優先順序

(1)預設使用預設快取,當匯入其他依賴則預設快取失效

(2)匯入多種依賴,則按照上面排序生效其中一個

// 原始碼
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {
    SimpleCacheConfiguration() {
}

  原始碼中,使用了註解@ConditionalOnMissingBean,其作用是:判斷當前需要注入Spring容器中的bean的實現類是否已經含有,有的話不注入,沒有就注入(被覆蓋)。當注入其他快取後,會在容器中注入對應快取的CacheManager,此時,由於上述註解的原因,預設快取則不再生效。

4、快取結構:Cache結構是一個map: ConcurrentMap<Object, Object>

5、核心思想:

  當我們呼叫一個方法時會把該方法的引數和返回結果作為一個鍵值對存放在快取中,等下次利用同樣的引數來呼叫該方法時將不會再執行,而是直接從快取中獲取結果進行返回。

  理解:springboot 的快取機制是通過切面程式設計 aop 來實現的。

二、SpringCache 註解

  Spring Cache 提供了 @Cacheable 、@CachePut 、@CacheEvict 、@Caching 等註解,在方法上使用。

  基於註解方式SpringCache引入Redis做快取,需要先了解@EnableCaching、@CacheConfig、@Cacheable、@CachePut、@CacheEvict、@Caching相關注解的使用。

1、@EnableCaching

  開啟快取功能,一般放在啟動類上或者自定義的RedisConfig配置類上。

2、@CacheConfig:在類上使用,當該類有多個方法有同樣的快取操作時使用

  當我們需要快取的地方越來越多,可以使用@CacheConfig(cacheNames = "cacheName") 註解在 class 之上來統一指定value的值,統一管理keys,這時可省略value,如果你在你的方法依舊寫上了value,那麼依然以方法的value值為準。

@Service
@CacheConfig(cacheNames = "categories")
public class CategoryServiceImpl implements CategoryService{}

3、@Cacheable:用於查詢資料,新增快取

  根據方法對其返回結果進行快取,下次請求時,如果快取存在,則直接讀取快取資料返回;如果快取不存在,則執行方法,並把返回的結果存入快取中。一般用在查詢方法上。

4、@CachePut:用於新增資料時, 自動更新快取

  使用該註解標誌的方法,每次都會執行,並將結果存入指定的快取中。其他方法可以直接從響應的快取中讀取快取資料,而不需要再去查詢資料庫。

  一般用在新增方法上。 檢視原始碼,屬性值同上@Cacheable差不多。

5、@CacheEvict:用於修改或刪除資料時,清空快取

  使用該註解標誌的方法,會清空指定的快取。一般用在更新或者刪除方法上。

  檢視原始碼,屬性值與@Cacheable差不多,獨有的兩個屬性如下:

  allEntries是否清空所有快取,預設為false。如果指定為true,則方法呼叫後將立即清空所有的快取

  beforeInvocation是否在方法執行前就清空所有快取,預設為false。如果指定為true,則方法執行前就會清空所有的快取

6、@Caching:該註解可以實現同一個方法上同時使用多種註解

// 可從其原始碼看出
public @interface Caching {
    Cacheable[] cacheable() default {};
    CachePut[] put() default {};
    CacheEvict[] evict() default {};
}
@Caching(evict = {
  @CacheEvict(cacheNames = "eventAskMaxId", key = "#ask.eventId", condition = "#ask.eventId != null"),
  @CacheEvict(cacheNames = "eventAskTotal", key = "#ask.eventId", condition = "#ask.eventId != null")
})
public void insert(EventAsk ask) {}

三、SpringBoot快速整合Redis

1、匯入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、新增配置:連線池引數要選擇lettuce的(預設),不要配成jedis的,原始碼中jedis很多類不存在,因此是不生效的。

3、使用:(1)快取:直接使用SpringBoot的快取註解即可;(2)操作:使用 RedisTemplate 或 RedisUtils 操作資料

  需要注意的是:實體類要實現Serializable(序列化)才能存入redis, 不然會報錯

public class Table implements Serializable {}

四、如何編寫Redis配置檔案

1、配置檔案解決核心問題:

(1)實現序列化後,redis工具中顯示亂碼:重寫redisTemplate方法,使用fastjson進行序列化解決

(2)設定預設過期時間

(3)Redis 掛後,報錯且不走資料庫:捕獲Redis連線超時的異常,使Redis掛掉後,不影響正常訪問

2、序列化問題原始碼分析:

(1)預設使用的RedisTemplate設定的JDK序列化(序列化效果不好),可以自定義redisTemplate替換預設的

(2)物件的兩個泛型都是Object,需要強制轉換為<String, Object>

(3)由於String 是redis最常用的型別,所以單獨提供了StringRedisTemplate

// RedisAutoConfiguration原始碼
@Bean
@ConditionalOnMissingBean( name = {"redisTemplate"} )// 可以自定義redisTemplate替換預設的
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    // 預設沒有做額外的序列化(使用的RedisTemplate預設序列化),redis物件都是需要序列化的
    // 兩個泛型都是Object,需要強制轉換為<String, Object>
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
@ConditionalOnMissingBean
// 由於String 是redis最常用的型別,所以單獨提供了StringRedisTemplate
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}
// RedisTemplate原始碼
if (this.defaultSerializer == null) { // RedisTemplate預設配置了JDK序列化 this.defaultSerializer = new JdkSerializationRedisSerializer(...); }

3、自定義 Redis 的序列化及配置

(1)使用fastjson序列化redis

(2)Redis 配置檔案

public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {}

@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
    // 1、解決序列化問題
    @Bean(name = "redisTemplate")
    @SuppressWarnings("unchecked")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //使用fastjson序列化
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        // ......
        return template;
    }

    // 2、設定key預設過期時間
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
            this.redisCacheConfiguration(1800),     // 預設配置
            this.initialCacheConfigurations());    // 指定key過期時間配置
    }
    // 3、配置redis掛掉後,捕獲異常,不影響查詢資料庫
    public CacheErrorHandler errorHandler() {
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
      ......
        };
        return cacheErrorHandler;
    }

}

  注意上面是虛擬碼,專案上的程式碼就不方便貼咯。