1. 程式人生 > >在SpringBoot中配置多個cache,實現多個cacheManager靈活切換

在SpringBoot中配置多個cache,實現多個cacheManager靈活切換

SpringBoot配置多個cache,實現多個cacheManager靈活切換

注:本文所用的springBoot版本號為1.5.6.6

目的&效果

在springBoot中同時配置了RedisCache和ehCache,當使用@Cacheable註解時,預設為redisCache,通過指定註解中"cacheManager"的屬性值,達到任意切換cache的效果。

第一次嘗試

由於@Cacheable註解是可以通過引數指定CacheManager或CacheResolver的,因此推斷springCache本身支援配置多個CacheManager。

首先嚐試直接配置RedisCacheManager和EhCacheCacheManager,結果啟動報錯:
Alt text


定位到該行報錯程式碼,原來是在CacheAspectSupport.afterSingletonsInstantiated方法,通過beanFactory獲取CacheManager.class的bean時,由於配置了多個bean,丟擲了一個NoUniqueBeanDefinitionException異常。
Alt text

分析

理論上只要在其中一個CacheManager的bean上加上@primary註解就可以解決這個報錯了。 但是這樣解決看起來是不優雅的。

從程式碼中看,在CacheAspectSupport.afterSingletonsInstantiated方法中,會先通過getCacheResolver方法嘗試獲取類中的成員變數cacheResolver,如果沒有獲取到,才會通過getBean方法嘗試獲取預設的cacheManager並通過cacheManager建立一個cacheResolver。 也就是說,在此之前是有地方設定預設cacheManager的。

一路檢視原始碼,最終發現, springCache會在配置類ProxyCachingConfiguration通過@Bean例項化一個CacheInterceptor(CacheAspectSupport的子類),並給interceptor的cacheResolver、cacheManager等成員變數賦值:

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor() {
		CacheInterceptor interceptor = new CacheInterceptor
(); interceptor.setCacheOperationSources(cacheOperationSource()); if (this.cacheResolver != null) { interceptor.setCacheResolver(this.cacheResolver); } else if (this.cacheManager != null) { interceptor.setCacheManager(this.cacheManager); } if (this.keyGenerator != null) { interceptor.setKeyGenerator(this.keyGenerator); } if (this.errorHandler != null) { interceptor.setErrorHandler(this.errorHandler); } return interceptor; }

而用來賦值的cacheManager等,是在ProxyCachingConfiguration的父類,抽象配置類AbstractCachingConfiguration裡面獲取到預設值的:

	@Autowired(required = false)
	void setConfigurers(Collection<CachingConfigurer> configurers) {
		if (CollectionUtils.isEmpty(configurers)) {
			return;
		}
		if (configurers.size() > 1) {
			throw new IllegalStateException(configurers.size() + " implementations of " +
					"CachingConfigurer were found when only 1 was expected. " +
					"Refactor the configuration such that CachingConfigurer is " +
					"implemented only once or not at all.");
		}
		CachingConfigurer configurer = configurers.iterator().next();
		useCachingConfigurer(configurer);
	}

	/**
	 * Extract the configuration from the nominated {@link CachingConfigurer}.
	 */
	protected void useCachingConfigurer(CachingConfigurer config) {
		this.cacheManager = config.cacheManager();
		this.cacheResolver = config.cacheResolver();
		this.keyGenerator = config.keyGenerator();
		this.errorHandler = config.errorHandler();
	}

因此,只需實現CachingConfigurer配置介面編寫配置類,並重寫其cacheManager方法提供預設的cacheManager就可以指定預設的cacheManager了。

最終方案

step0 首先確保maven中已經引入了相關jar包依賴,主要包括redis和ehcache的相關依賴。
        <!--快取,redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.4</version>
        </dependency>
step1 在application.yml中新增redis的連線配置和ehcache的配置。
spring:
    redis:
      # Connection URL, will override host, port and password (user will be ignored), e.g. redis://user:[email protected]:6379
      url: redis://localhost:6379
      pool:
            maxActive: 8  # 連線池最大連線數(使用負值表示沒有限制)
            maxWait: 800 # 連線池最大阻塞等待時間(使用負值表示沒有限制)
            maxIdle: 8 # 連線池中的最大空閒連線
            minIdle: 2 # 連線池中的最小空閒連線
      timeout: 2000 # 連線或讀取超時時長(毫秒)

    cache:
        type: ehcache
        ehcache:
          config: ehcache.xml
step2 在專案的resources目錄下,新增ehcache.xml配置檔案。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!-- 指定一個檔案目錄,當EhCache把資料寫到硬碟上時,將把資料寫到這個檔案目錄下 -->
    <!--<diskStore path="logs/ehcache"/>-->

    <!-- 設定快取的預設資料過期策略 -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="10"
            timeToLiveSeconds="20"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"/>

    <cache name="cp_salary:cache:10m"
           maxElementsInMemory="100000"
           eternal="false"
           overflowToDisk="false"
           timeToIdleSeconds="10"
           timeToLiveSeconds="600"/>

    <cache name="cp_salary:cache:20m"
           maxElementsInMemory="100000"
           eternal="false"
           overflowToDisk="false"
           timeToIdleSeconds="10"
           timeToLiveSeconds="1200"/>

    <cache name="cp_salary:cache:30m"
           maxElementsInMemory="100000"
           eternal="false"
           overflowToDisk="false"
           timeToIdleSeconds="10"
           timeToLiveSeconds="1800"/>

</ehcache>
step3 編寫CacheManagerConfig類,在類裡通過@Bean註解生成RedisCacheManager和EhCacheCacheManager的bean。參考程式碼如下。
package com.lianjia.sh.salary.payroll.config;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import com.google.common.collect.ImmutableMap;
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Stephen.Shi
 * @summary CacheManager配置
 * @since 2018/9/9
 */
@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class CacheManagerConfig {
    private final CacheProperties cacheProperties;

    CacheManagerConfig(CacheProperties cacheProperties) {
        this.cacheProperties = cacheProperties;
    }

    /**
     * cacheManager名字
     */
    public interface CacheManagerNames {
        /**
         * redis
         */
        String REDIS_CACHE_MANAGER = "redisCacheManager";

        /**
         * ehCache
         */
        String EHCACHE_CACHE_MAANGER = "ehCacheCacheManager";
    }

    /**
     * 快取名,名稱暗示了快取時長 注意: 如果添加了新的快取名,需要同時在下面的RedisCacheCustomizer#RedisCacheCustomizer裡配置名稱對應的快取時長
     * ,時長為0代表永不過期;快取名最好公司內部唯一,因為可能多個專案共用一個redis。
     *
     * @see RedisCacheCustomizer#customize(RedisCacheManager)
     */
    public interface CacheNames {
        /** 15分鐘快取組 */
        String CACHE_15MINS = "cp_salary:cache:15m";
        /** 30分鐘快取組 */
        String CACHE_30MINS = "cp_salary:cache:30m";
        /** 60分鐘快取組 */
        String CACHE_60MINS = "cp_salary:cache:60m";
        /** 180分鐘快取組 */
        String CACHE_180MINS = "cp_salary:cache:180m";
    }

    /**
     * ehcache快取名
     */
    public interface EhCacheNames {
        String CACHE_10MINS = "cp_salary:cache:10m";

        String CACHE_20MINS = "cp_salary:cache:20m";

        String CACHE_30MINS = "cp_salary:cache:30m";
    }

    @Bean
    public RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 預設的redisCacheManager
     * @param redisTemplate 通過引數注入,這裡沒有手動給它做配置。在引入了redis的jar包,並且往
     * application.yml裡添加了spring.redis的配置項,springboot的autoconfig會自動生成一個
     * redisTemplate的bean
     * @return
     */
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate<Object, Object> redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setUsePrefix(true);
        this.redisCacheManagerCustomizer().customize(cacheManager);
        return cacheManager;
    }

    /** cache的一些自定義配置 */
    @Bean
    public RedisCacheCustomizer redisCacheManagerCustomizer() {
        return new RedisCacheCustomizer();
    }

    private static class RedisCacheCustomizer
            implements CacheManagerCustomizer<RedisCacheManager> {
        /** CacheManager快取自定義初始化比較早,儘量不要@autowired 其他spring 元件 */
        @Override
        public void customize(RedisCacheManager cacheManager) {
            // 自定義快取名對應的過期時間
            Map<String, Long> expires = ImmutableMap.<String, Long>builder()
                    .put(CacheNames.CACHE_15MINS, TimeUnit.MINUTES.toSeconds(15))
                    .put(CacheNames.CACHE_30MINS, TimeUnit.MINUTES.toSeconds(30))
                    .put(CacheNames.CACHE_60MINS, TimeUnit.MINUTES.toSeconds(60))
                    .put(CacheNames.CACHE_180MINS, TimeUnit.MINUTES.toSeconds(180)).build();
            // spring cache是根據cache name查詢快取過期時長的,如果找不到,則使用預設值
            cacheManager.setDefaultExpiration(TimeUnit.MINUTES.toSeconds(30));
            cacheManager.setExpires(expires);
        }
    }

	/**
	* 建立ehCacheCacheManager
	*/
    @Bean
    public EhCacheCacheManager ehCacheCacheManager() {
        Resource location = this.cacheProperties
                .resolveConfigLocation(this.cacheProperties.getEhcache().getConfig());
        return new EhCacheCacheManager(EhCacheManagerUtils.buildCacheManager(location));
    }
}
step4 編寫CacheConfig.java,配置預設的cacheManager
package com.lianjia.sh.salary.payroll.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;

/**
 * Spring cache的一些配置,建議元件相關配置都放在相應的configuration類中
 *
 * @author
 */
@Configuration
@ConditionalOnBean(RedisCacheManager.class)
public class CacheConfig extends CachingConfigurerSupport {
  @Autowired
  private RedisCacheManager redisCacheManager;

  /**
   * 重寫這個方法,目的是用以提供預設的cacheManager
   * @author Stephen.Shi
   * @return
   */
  @Override
  public CacheManager cacheManager() {
    return redisCacheManager;
  }

  /** 如果cache出錯, 我們會記錄在日誌裡,方便排查,比如反序列化異常 */
  @Override
  public CacheErrorHandler errorHandler() {
    return new LoggingCacheErrorHandler();
  }


  /* non-public */ static class LoggingCacheErrorHandler extends SimpleCacheErrorHandler {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
      logger.error(String.format("cacheName:%s,cacheKey:%s",
          cache == null ? "unknown" : cache.getName(), key), exception);
      super.handleCacheGetError(exception, cache, key);
    }

    @Override
    public void handleCachePutError(RuntimeException exception, Cache cache, Object key,
        Object value) {
      logger.error(String.format("cacheName:%s,cacheKey:%s",
          cache == null ? "unknown" : cache.getName(), key), exception);
      super.handleCachePutError(exception, cache, key, value);
    }

    @Override
    public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
      logger.error(String.format("cacheName:%s,cacheKey:%s",
          cache == null ? "unknown" : cache.getName(), key), exception);
      super.handleCacheEvictError(exception, cache, key);
    }

    @Override
    public void handleCacheClearError(RuntimeException exception, Cache cache) {
      logger.error(String.format("cacheName:%s", cache == null ? "unknown" : cache.getName()),
          exception);
      super.handleCacheClearError(exception, cache);
    }
  }
}

step5 編寫demo,檢視效果
package com.lianjia.sh.salary.payroll.service;

import com.lianjia.sh.salary.payroll.config.CacheManagerConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * @author shitiecheng
 * @version v1
 * @since 2018/7/31
 */
@Service
public class CacheDemoService {

    @Cacheable(key = "key", cacheManager = CacheManagerConfig.CacheManagerNames.EHCACHE_CACHE_MAANGER, cacheNames = CacheManagerConfig.EhCacheNames.CACHE_10MINS)
    public String demo(String key) {
        return "abc" + key;
    }

    @Cacheable(key = "key", cacheNames = CacheManagerConfig.CacheNames.CACHE_15MINS)
    public String demo2(String key) {
        return "abc" + key;
    }
}

結果:

相關推薦

SpringBoot配置cache實現cacheManager靈活切換

SpringBoot配置多個cache,實現多個cacheManager靈活切換 注:本文所用的springBoot版本號為1.5.6.6 目的&效果 在springBoot中同時配置了RedisCache和ehCache,當使用@Cacheable註解

Linux6.5配置PXE自動裝機實現批量裝機服務。

linu type 大量 oss 9.png tex 技術 批量裝機 watermark 為了增加工作效率,我們在進行linux裝機時,在面臨大量裸機的情況下, PXE自動裝機的作用尤為顯著,能夠大大減少我們工作壓力,增加工作效率。 安裝環境:linux6.5一臺,w

springboot配置addResourceHandler和addResourceLocations使得可以從磁碟讀取圖片、視訊、音訊等

磁碟目錄 WebMvcConfig的程式碼 //對靜態資源的配置 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String os = System.ge

Springboot整合Spring data elasticsearch實現相關CRUD介面

專案環境: JDK:1.8 SringBoot:2.1.0.RELEASE Gardle:gradle-4.10.2 ElasticSearch:elasticsearch-6.2.4 Spring-data-elasticsearch:spring-data-el

記一次java socket學習(簡單實用執行緒實現群聊)

學習過程是艱苦,學習結束是快樂的 目錄 用 [TOC]來生成目錄: 本來想寫一些文字描述描述,可是想不出來說啥。。。所以直接記錄程式碼了。。。 程式碼塊 因為喜歡把常量都提取出來 所以上來就是常量類: public class Const

Spring Boot 配置定時任務實現線程操作

pre log pri http code china 部分 多線程操作 .net 參考的代碼部分 https://git.oschina.net/jokerForTao/spring_boot_schedule 一目了然!Spring Boot 中配置定時任務,實現

配置Apache虛擬主機實現在一臺服務器上運行網站

Apache多實例演示Apache虛擬主機實現有三種方法:1、通過不同的IP地址2、通過不同的域名3、通過不同的端口號 1、通過不同的IP地址,解析不同的域名(1)給服務器增加IP(另一個域名解析)[root@http ~]# ifconfig eth0:1 192.168.2.12查看添加成功(2)創建測試

Python爬蟲:在帶有屬性值的class選擇器選擇其中一個值實現標籤快速精準定位

在寫爬蟲時,定位標籤位置獲取想要的資料是匹配工作的重點。通過class或id選擇器的值(屬性值要求在網頁中是唯一的)可以很快的實現標籤的定位。 <span data-post-id="114214" class=" btn-bluet-bigger href-styl

【android-Webview】設定cookie實現webview儲存登入資訊

方法:通過重複呼叫 cookieManager.setCookie(url,cookie1); 來儲存多個cookie。 程式碼: /** * Sync Cookie */ private void syncCookie(Context

在struts2配置自定義攔截器放行方法

return med ttr limit ring req tac cat invoke 源碼: 自定義的攔截器類: //自定義攔截器類:LoginInterceptor ; package com.java.action.interceptor; import j

windows環境建立redis例項實現主從同步

redis單機環境下開啟多個例項,並形成主從同步。redis預設埠是6379,這裡我們建立了三個例項,分別使用了6380,6381,6382三個埠,6380埠例項作為主節點,6381和6382作為從節點。 1.下載好windows版本的redis,進行解壓。這裡並沒有安裝為windows的服務,

C語言實現整數m和n的二進位制序列有多少不同的位

新手學程式設計,大佬寫的太高深,看看本人小白寫的,一起學習一起交流 #include <stdio.h> int main() { int m = 0; int n = 0; int i = 0; int count = 0; scanf("%d%d", &

Java不使用陣列時傳入可變引數

JDK1.5之後   Public static void main(String[] args) { Demo(); Demo(1,2,3); } public void Demo(int a,int b,int...array)//放最後,只能一個 {

springboot配置好登入攔截後swagger訪問不了

錯誤資訊: java.lang.ClassCastException: org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to org.springframework.web.me

frp客戶端實現一臺雲伺服器連線區域網內的主機埠轉發

很簡單,用不同的frps.ini就行 假設你原來已經啟動了一個frps.ini,現在想再加一個。 那就新建一個frps1.ini,然後埠和之前的不一樣就行。 假設之前有一個7000的埠了,那現在這個用7001就可以啦~ cp frps.ini frps1.ini 如

mysql正則表示式實現欄位匹配like模糊查詢

現在有這麼一個需求 一個questions表,欄位有題目(TestSubject),選項(AnswerA,AnswerB,AnswerC,AnswerD,AnswerE) 要求欄位不包含png,jpg,jpeg,gif  正常情況下會這麼寫 select * from questions

微信小程式實現按鈕toggle功能

如下圖所示,實現該按鈕toggle功能。 百度上很多都是隻設定一個按鈕的toggle,所以我現在來稍微總結下:多個按鈕如何實現自身的toggle功能。 原理:1,列表展示的時候,我們會用wx:for 來迴圈資料,那麼我們就會得到相應的當前的第幾個資料(即 wx:key="

SpringBoot進階教程 | 第一篇:YML文件塊實現環境配置

你是否為SpringBoot一個功能多個yml和多個properties檔案區分不同執行環境配置,經常為這些配置檔案的管理而頭疼,現在通過這篇文章,將徹底解決你的煩惱,這篇文篇介紹,怎麼通過yml檔案構建多文件塊,區分不同環境配置,自由切換不同環境啟動專案,一個

【koa2】koa-static-router 中介軟體搭建靜態資源伺服器實現&&層路由載入靜態資源

koa中介軟體koa-static-router中介軟體搭建靜態資源伺服器,實現多個&&多層路由載入靜態資源 安裝 $ npm install koa-static-

Linux:使用執行緒程式設計和訊息佇列實現程序之間的聊天

思路: 一個檔案:建立一個執行緒和主函式,或者建立兩個執行緒主函式呼叫(我用這種)。 建立兩個訊息佇列, 一共兩個檔案,兩個佇列,四個程序 a.c    一個程序寫(訊息型別為1)   ---->>佇列     一個程序讀(訊息型別為2) b.c   一