spring-boot-redis-cache集成总结及源码分析
目錄
緩存
基于spring的Cache進行緩存(基于注解)
Spring緩存支持
聲明式緩存注解
先看看如何使用
1、要想使用Cache緩存,那首先必須的開啟吧
在看看別人的例子
源碼分析
@EnableCaching
由于Cache是通過CacheManager進行管理,在springboot的autoConfig包中,來看看cache的自動配置
cacheManager咋個來的
自定義(解決默認序列化)JdkSerializationRedisSerializer
如何自定義虛擬化
緩存
- 內存的速度遠遠大于硬盤的速度
- 緩存主要是在獲取資源方便性能優化的關鍵方面
- Redis 是緩存數據庫
- 緩存未命中解決與防止緩存擊穿
基于spring的Cache進行緩存(基于注解)
Spring緩存支持
Spring定義了org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口來統一不同緩存技術。
其中CacheManager是Spring提供的各種緩存技術抽象接口,內部使用Cache接口進行緩存的增刪改查操作,我們一般不會直接和Cache打交道。
針對不同的緩存技術,Spring有不同的CacheManager實現類,定義如下表:
| SimpleCacheManager | 使用簡單的Collection存儲緩存數據,用來做測試用 |
| ConcurrentMapCacheManager | 使用ConcurrentMap存儲緩存數據 |
| EhCacheCacheManager | 使用EhCache作為緩存技術 |
| GuavaCacheManager | 使用Google Guava的GuavaCache作為緩存技術 |
| JCacheCacheManager | 使用JCache(JSR-107)標準的實現作為緩存技術,比如Apache Commons JCS |
| RedisCacheManager | 使用Redis作為緩存技術【本次使用的】 |
聲明式緩存注解
Spring提供4個注解來聲明緩存規則,如下表所示:
| @Cacheable | 方法執行前先看緩存中是否有數據,如果有直接返回。如果沒有就調用方法,并將方法返回值放入緩存 |
| @CachePut | 無論怎樣都會執行方法,并將方法返回值放入緩存 |
| @CacheEvict | 將數據從緩存中刪除 |
| @Caching | 可通過此注解組合多個注解策略在一個方法上面 |
@Cacheable 、@CachePut 、@CacheEvict都有value屬性,指定要使用的緩存名稱,而key屬性指定緩存中存儲的鍵。
@EnableCaching 開啟緩存。
@Cacheable
這個注解含義是方法結果會被放入緩存,并且一旦緩存后,下一次調用此方法,會通過key去查找緩存是否存在,如果存在就直接取緩存值,不再執行方法。
這個注解有幾個參數值,定義如下
| cacheNames | 緩存名稱 |
| value | 緩存名稱的別名 |
| condition | Spring SpEL 表達式,用來確定是否緩存 |
| key | SpEL 表達式,用來動態計算key |
| keyGenerator | Bean 名字,用來自定義key生成算法,跟key不能同時用 |
| unless | SpEL 表達式,用來否決緩存,作用跟condition相反 |
| sync | 多線程同時訪問時候進行同步 |
在計算key、condition或者unless的值得時候,可以使用到以下的特有的SpEL表達式
| #result | 表示方法的返回結果 |
| #root.method | 當前方法 |
| #root.target | 目標對象 |
| #root.caches | 被影響到的緩存列表 |
| #root.methodName | 方法名稱簡稱 |
| #root.targetClass | 目標類 |
| #root.args[x] | 方法的第x個參數 |
@CachePut
該注解在執行完方法后會觸發一次緩存put操作,參數跟@Cacheable一致
@CacheEvict
該注解在執行完方法后會觸發一次緩存evict操作,參數除了@Cacheable里的外,還有個特殊的allEntries, 表示將清空緩存中所有的值。
【引用】吃水不忘挖井人:感謝
在SpringBoot中配置多個cache,實現多個cacheManager靈活切換
好文章,直接學習人家的總結
官方文檔對cache的說明
先看看如何使用
1、要想使用Cache緩存,那首先必須的開啟吧
@EnableCaching
2、由于Cache是通過CacheManager進行管理,在springboot的autoConfig包中,來看看cache的自動配置
CacheManager需要一個cacheManager對象(bean)【看下面源碼分析,有詳細說明】
3、在需要加入緩存的地方加上相關注解即可
@Cacheable(value = "a:obj", key = "#cacheName") @RequestMapping("/k") public Object cacheobjct(String cacheName) {redisTemplate.opsForValue().set("a:wolf:sty01", "wolf-spring-boot");return new User("鄭先生", "正顯示測試緩存", 18); }//我這里就是在controller的方法上加的,最終執行后,在redis就給我創建好了這個緩存數據4、查看結果
(1)下次在啟動,只要redis里面有這個key的值,直接通過redis加裝,如果沒得話,就通過執行具體邏輯加載,然后在放到redis中
在看看別人的例子
這個就數據連接池、緩存配置類、cacheMangaer全部都自己配,不用springboot底層的默認配置
反過來想,這其實沒必要的。能夠使用底層配置好了的就最好使用配置好了的,除非你不懂。拷貝人家代碼,結果代碼可以正常運行就不管了。俗話說:知其然,必要知其所以然;雖然我不是大牛,但是這就是我學習的方式和初衷;讓學習代碼變得快樂,不枯燥。【2021年2月25日記】--多年后,自己再看看,看看還有啥子心得。
源碼分析
@EnableCaching
通過開啟Cache,然后程序加載掃描所有的類或者方法上加了緩存注解的,然后給他通過AOP切面的方式進行動態代理,讓具體執行的方法具有Cache的功能
由于Cache是通過CacheManager進行管理,在springboot的autoConfig包中,來看看cache的自動配置
先看看傳統基于xml配置的cacheManager
既然用了springboot那他底層是如何生成cacheManager【重點在這,我要一個bean】的
cacheManager咋個來的
【源碼源碼】
//他是一共配置類 @Configuration(proxyBeanMethods = false ) //當前系統環境存在這個class的時候才會啟用這個配置類【這些都是springboot用的最多,最基礎的東西,必須要掌握,如果不知道,先學習基礎。不然下面這些注解,會看不懂 @ConditionalOnClass({CacheManager.class}) //當前系統環境存在CacheAspectSupport這個class的時候,配置類啟用做 @ConditionalOnBean({CacheAspectSupport.class}) //如果容器中沒得CacheManager.class類型的bean才進行加載或者beanName=cacheResolver的 @ConditionalOnMissingBean(value = {CacheManager.class},name = {"cacheResolver"} ) //開啟自動加載配置文件:把當前系統環境配置文件的信息,按CacheProperties配置類定義的規則加載到CacheProperties中 //CacheProperties ?@ConfigurationProperties(prefix = "spring.cache")加載已spring.cache開頭的配置信息,然后封裝到CacheProperties對象的屬性中【這個用的也多,很重要】 @EnableConfigurationProperties({CacheProperties.class}) //由于CacheManager需要數據連接,所以這個配置必須在下面這些數據連接配置好了以后在加載 @AutoConfigureAfter({CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class}) //給容器導入下面這個2個bean @Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class, CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnPostProcessor.class}) public class CacheAutoConfiguration {public CacheAutoConfiguration() {}@Bean@ConditionalOnMissingBeanpublic CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {return new CacheManagerCustomizers((List)customizers.orderedStream().collect(Collectors.toList()));}@Beanpublic CacheAutoConfiguration.CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {return new CacheAutoConfiguration.CacheManagerValidator(cacheProperties, cacheManager);}static class CacheConfigurationImportSelector implements ImportSelector {CacheConfigurationImportSelector() {}public String[] selectImports(AnnotationMetadata importingClassMetadata) {CacheType[] types = CacheType.values();String[] imports = new String[types.length];for(int i = 0; i < types.length; ++i) {imports[i] = CacheConfigurations.getConfigurationClass(types[i]);}/** imports的值,緩存配置類org.springframework.boot.autoconfigure.cache.GenericCacheConfigurationorg.springframework.boot.autoconfigure.cache.JCacheCacheConfigurationorg.springframework.boot.autoconfigure.cache.EhCacheCacheConfigurationorg.springframework.boot.autoconfigure.cache.HazelcastCacheConfigurationorg.springframework.boot.autoconfigure.cache.InfinispanCacheConfigurationorg.springframework.boot.autoconfigure.cache.CouchbaseCacheConfigurationorg.springframework.boot.autoconfigure.cache.RedisCacheConfigurationorg.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration 【redis環境引入--它變成了默認了】org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration 【默認】---通過ImportSelector給容器導入SimpleCacheConfiguration這個bean【但是---但是】org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration---由于我當前項目環境引入了redis相關信息及默認有redicConnectionFactory的存在,也就是bean按順序創建的時候,RedisCacheConfiguration這個配置先于SimpleCacheConfiguration加載,那恰好RedisCacheConfiguration滿足條件【看下面源碼剖析】*/return imports;}}static class CacheManagerValidator implements InitializingBean {private final CacheProperties cacheProperties;private final ObjectProvider<CacheManager> cacheManager;CacheManagerValidator(CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {this.cacheProperties = cacheProperties;this.cacheManager = cacheManager;}public void afterPropertiesSet() {Assert.notNull(this.cacheManager.getIfAvailable(), () -> {return "No cache manager could be auto-configured, check your configuration (caching type is \'" + this.cacheProperties.getType() + "\')";});}}@ConditionalOnClass({LocalContainerEntityManagerFactoryBean.class})@ConditionalOnBean({AbstractEntityManagerFactoryBean.class})static class CacheManagerEntityManagerFactoryDependsOnPostProcessor extends EntityManagerFactoryDependsOnPostProcessor {CacheManagerEntityManagerFactoryDependsOnPostProcessor() {super(new String[]{"cacheManager"});}} }//在看看SimpleCacheConfiguration的定義嘛 //那想為什么SimpleCacheConfiguration是默認是呢? mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName()); mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class.getName()); mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName()); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName()); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName()); mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName()); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName()); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName()); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName()); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName()); 【請分別看這些配置類上面的條件:恰恰只有SimpleCacheConfiguration在程序啟動的時候滿足條件】@Configuration(proxyBeanMethods = false ) @ConditionalOnMissingBean({CacheManager.class}) @Conditional({CacheCondition.class}) class SimpleCacheConfiguration {SimpleCacheConfiguration() {}//給容器創建一個ConcurrentMapCacheManager--CacheManager【基于內存,線程安全的緩存管理器】@BeanConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) {ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();List cacheNames = cacheProperties.getCacheNames();if(!cacheNames.isEmpty()) {cacheManager.setCacheNames(cacheNames);}return (ConcurrentMapCacheManager)cacheManagerCustomizers.customize(cacheManager);} }//【】 @Configuration(proxyBeanMethods = false ) @ConditionalOnClass({RedisConnectionFactory.class})//加入redis以后,springboot底層會通過RedisAutoConfiguration自動配置的@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) //導入了JedisConnectionFactory redisConnectionFactory的連接工廠 @AutoConfigureAfter({RedisAutoConfiguration.class})//然后恰好,這個配置類由在RedisAutoConfiguration配置類完成之后,在進行加載的 @ConditionalOnBean({RedisConnectionFactory.class})//這個時候,在redis自動配置類中,已經完成了RedisConnectionFactory的創建 @ConditionalOnMissingBean({CacheManager.class})//容器中呢,由不存在bean類型為CacheManager的bean @Conditional({CacheCondition.class})//這個是一個條件,也就是滿足CacheCondition里面自定義的條件后,這個配置類才會加載 class RedisCacheConfiguration {RedisCacheConfiguration() {}//重點在這里,這里創建了基于redis的RedisCacheManager//【重點分析】//1、cacheProperties把基于spring.cache的配置文件類加載進來,這里初始化的時候需要用到里面的參數//2、@BeanRedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers, RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));List cacheNames = cacheProperties.getCacheNames();if(!cacheNames.isEmpty()) {builder.initialCacheNames(new LinkedHashSet(cacheNames));}if(cacheProperties.getRedis().isEnableStatistics()) {builder.enableStatistics();}redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> {customizer.customize(builder);});//這里最后就返回了基于RedisCacheManagerreturn (RedisCacheManager)cacheManagerCustomizers.customize(builder.build());}//基于配置信息生成了RedisCacheConfiguration配置private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(CacheProperties cacheProperties, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ClassLoader classLoader) {return (org.springframework.data.redis.cache.RedisCacheConfiguration)redisCacheConfiguration.getIfAvailable(() -> {return this.createConfiguration(cacheProperties, classLoader);});}//創建配置信息:主要是cacheManagerCustomizers.customize(builder.build())這個里面會使用配置信息里面設置的屬性private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(CacheProperties cacheProperties, ClassLoader classLoader) {//拿到redis的配置信息:從配置類里面拿(配置類里面包含redis配置信息)Redis redisProperties = cacheProperties.getRedis();//這里創建了一個默認的RedisCacheConfigurationorg.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig();//設置序列化保存值的時候,自用的哪種序列化方式【重點來了--------假如說:我們要自定義保存值或者設置key使用的序列化策略的時候,可以通過修改勒個進行調整嘛】//【再說一次,這里是重點:后面可能由于這種默認序列化策略不好,保存到redis的值,看起不舒服,想通過自定義的策略方式來搞一搞】config = config.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));if(redisProperties.getTimeToLive() != null) {config = config.entryTtl(redisProperties.getTimeToLive());}if(redisProperties.getKeyPrefix() != null) {config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());}if(!redisProperties.isCacheNullValues()) {config = config.disableCachingNullValues();}if(!redisProperties.isUseKeyPrefix()) {config = config.disableKeyPrefix();}return config;} }【源碼結束】
自定義(解決默認序列化)JdkSerializationRedisSerializer
JdkSerializationRedisSerializer來嘛,加入我現在要分析問題;進入redis數據庫查看這個key的值,哪看得懂。所以要改一改默認基于JdkSerializationRedisSerializer的序列化方式
如何自定義虛擬化
思路和方法
1、想辦法給替換RedisCacheConfiguration
config.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)))//把JdkSerializationRedisSerializer替換成自己想實現的虛擬化方式
通過上面源碼里面有,但是反過來想。這個層級太復雜了,而且不好弄。那我直接重新自定義CacheManager,給cacheManager里面保存值的序列化方式進行設置就行了嘛
2、上代碼
//必須要定義個配置類【這個也可以加一些配置類加載策略-根據情況需要】 @Configuration public class CacheManagerConfig {//自定義RedisCacheManager@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {//序列化方式基于StringRedisSerializerRedisSerializer<String> strSerializer = new StringRedisSerializer();//序列化方式基于Jackson2JsonRedisSerializerJackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jacksonSeial.setObjectMapper(om);//下面是核心//創建一個默認的defaultCacheConfig【這個開始分析springboot默認源碼的時候看到過,直接用。不要去創造】RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()//設置過期時間(redis-Key)--也可以根據需要在注入CacheProperties cacheProperties【配置類信息,通過配置類信息獲取--這樣可以動態替換】//我研究的時候直接研究底層,具體的方式,生產級別使用。曉得以后咋個搞就行了。.entryTtl(Duration.ofDays(1))//【重點】這個就設置自己的序列化方式.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(strSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSeial)).disableCachingNullValues();//這里就是返回一個RedisCacheManager,這里搞這么多就是把你要的數據源給你,配置信息給你。底層管你怎么搞,看看源碼即可。//想不這么寫,隨便。看源碼,最終構建RedisCacheManager的時候,需要什么信息,你提供好就行,只是不通過人家底層的建造者模式(RedisCacheManagerBuilder)這個類來build()//讀源碼是讀思想,學思路。不是說怎么怎么滴【剖析有深有原理就夠了:學到思想到位】RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();return cacheManager;} }//【這里通過建造者模式:搞了一個復雜的對象出來】 public RedisCacheManager build() {Assert.state(this.cacheWriter != null, "CacheWriter must not be null! You can provide one via \'RedisCacheManagerBuilder#cacheWriter(RedisCacheWriter)\'.");RedisCacheWriter theCacheWriter = this.cacheWriter;if(!this.statisticsCollector.equals(CacheStatisticsCollector.none())) {theCacheWriter = this.cacheWriter.withStatisticsCollector(this.statisticsCollector);}RedisCacheManager cm = new RedisCacheManager(theCacheWriter, this.defaultCacheConfiguration, this.initialCaches, this.allowInFlightCacheCreation);cm.setTransactionAware(this.enableTransactions);return cm; }3、到此為止,自定義的cacheManager就在容器中存在了,然后其他自動加載類,既然容器中有了這個bean,那不會再加載了【先不考了有多個cacheManager存在的情況---實際是允許的】只是我沒記性分析下去了
因為實際的工作場景中,沒那么復雜。
關于配置類里面@ConditionalOnBean({CacheAspectSupport.class})的特別說明
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的spring-boot-redis-cache集成总结及源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: htcm7刷linux,htc one
- 下一篇: 咖说 | 新基建中区块链的位置:基础设施