Spring Boot 集成 Redis Cache 和 EhCache Cache

原創-轉載請注明 http://tramp.cincout.cn/2017/10/31/spring-boot-2017-10-31-spring-boot-multi-cache-manager/

摘要

Spring Cache 為企業級級應用提供了類似于 Spring Transactional 的聲明式緩存抽象層。Spring 采用 AOP 的方式實現對多種底層緩存技術的適配。包括 REDISCOUCHBASEEHCACHE 等。
當配置好 spring.cache.type=REDIS 時, Spring Boot 的自動裝配策略會自動的為我們配置好需要的底層緩存框架以及對應的CacheManager。這在大多數場景下是滿足需求的。
筆者在實際開發中遇到的場景是一些常量信息需要存儲到Redis中,利用Redis 的分布式緩存功能,使得多個節點可以共享該數據;一些需要基于特定的緩存清理規則(采用LRU存儲最近最常使用的10000 條)的數據則需要采用EhCache 實現。

原理

Spring Boot 的自動裝配

當引入 spring-boot-starter-data-redis 時,Spring Boot 會采用RedisAutoConfiguration 會我們配置好 Redis 的基礎配置信息。具體參見該類。在本項目中我們采用的時 EnCache 2.x, 因此需要我們單獨引入對應的依賴。
當引入 spring-boot-starter-cache,以及注解了@EnableCaching 時, Spring Boot 便會采用CacheAutoConfigurationRedisCacheConfiguration 來進行自n一如多個動裝配。裝配的條件是缺少 CacheManager.class 實例 Bean。

因此,當需要引入多個 CacheManager 的需要我們自己來分別配置。

實現

pom 依賴

需要引入的核心依賴如下,具體的參見后文的源代碼鏈接:

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

<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache-core -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.11</version>
</dependency>

application.properties

application.properties 可以配置底層 Redis 數據源的相關信息:

spring.redis.host=cincout.cn
spring.redis.port=6379

配置 CacheManager

分別配置 RedisCacheManagerEhCacheCacheManager

@Configuration
@EnableCaching
public class CacheConfig implements ApplicationRunner {

    @Resource
    private List<CacheManager> cacheManagers;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(cacheManagers.size());
    }

    @Bean(name = "redisCacheManager")
    public RedisCacheManager redisCacheManager(RedisTemplate<Object, Object> redisTemplate) {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
        redisCacheManager.setCacheNames(Arrays.asList("user"));
        redisCacheManager.setUsePrefix(true);
        return redisCacheManager;
    }

    @Bean(name = "ehcache")
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
        EhCacheManagerFactoryBean cacheBean = new EhCacheManagerFactoryBean();
        cacheBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        return cacheBean;
    }

    @Bean("ehCacheCacheManager")
    public EhCacheCacheManager ehCacheCacheManager(@Qualifier("ehcache") net.sf.ehcache.CacheManager ehcacheManager) {
        EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager(ehcacheManager);
        return ehCacheCacheManager;
    }

    @Bean(name = "cacheManager")
    @Primary
    public CompositeCacheManager cacheManager(RedisCacheManager redisCacheManager, EhCacheCacheManager ehCacheCacheManager) {
        CompositeCacheManager cacheManager = new CompositeCacheManager(redisCacheManager, ehCacheCacheManager);
        return cacheManager;
    }
}

encache.xml 配置文件的內容:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
    updateCheck="false">
    <defaultCache eternal="false" maxElementsInMemory="1000"
        overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
        timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" />

    <!--  200 * 10 := 2k per api metadata -->
    <cache name="api" maxElementsInMemory="10000" eternal="false"
        timeToIdleSeconds="0" timeToLiveSeconds="600" overflowToDisk="false"
        diskPersistent="false" diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </cache>
</ehcache>

該文件定義了名字為api 的cache 的緩存策略。具體的參見Ehcache

業務中使用

在業務代碼中使用時為了區分不同的業務使用不同的CacheManager 有兩種方式實現。

  • 在業務代碼中采用@CacheConfig, @CachePut, @Cacheable, @CacheEvict 來進行緩存的配置。它們都擁有cacheManager 這個屬性。
    具體配置:
@Service
@CacheConfig(cacheManager = "ehCacheCacheManager")
public class ApiMetaServiceImpl implements ApiMetaService {
    private final static Logger LOG = LoggerFactory.getLogger(ApiMetaServiceImpl.class);

    @Override
    @CachePut(cacheNames = "api", key = "#api.id.toString()")
    public Api save(Api api) {
        LOG.info("save {}", api);
        return api;
    }

    @Override
    @Cacheable(cacheNames = "api", key = "#id.toString()")
    public Api get(Integer id) {
        LOG.info("get {}", id);
        return new Api(1, "x");
    }
}

@Service
@CacheConfig(cacheManager = "redisCacheManager")
public class UserServiceImpl implements UserService {
    private final static Logger LOG = LoggerFactory.getLogger(UserServiceImpl.class);

    @Override
    @CachePut(cacheNames = "user", key = "#user.id.toString()")
    public User save(User user) {
        LOG.info("saved user {}", user);
        return user;
    }

    @Override
    @CacheEvict(cacheNames = "user")
    public void delete(int id) {
        LOG.info("delete {}", id);
    }

    @Override
    @Cacheable(cacheNames = "user")
    public User get(int id) {
        LOG.info("get user {}", id);
        return new User(1, "zhang");
    }
}
  • 采用CompositeCacheManagerSpring Cache在 CacheManager 之下可以采用緩存名稱(cacheNames 屬性)來對緩存進行區分。Spring Cache 提供了CompositeCacheManager 來對所有的 CacheManager 進行代理。
    根據指定的cacheName 去遍歷所有的 CacheManager,查找對應的緩存。

RedisCacheManager指定CacheNames

redisCacheManager.setCacheNames(Arrays.asList("user"));
redisCacheManager.setUsePrefix(true);

同時需要在緩存相關的注解上配置 cacheNames 屬性。

EnCache

EnCache 直接在其配置文件中指明:

<cache name="api" maxElementsInMemory="10000" eternal="false"
        timeToIdleSeconds="0" timeToLiveSeconds="600" overflowToDisk="false"
        diskPersistent="false" diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
</cache>

配置基于CompositeCacheManager 的CacheManager

@Bean(name = "cacheManager")
@Primary
public CompositeCacheManager cacheManager(RedisCacheManager redisCacheManager, EhCacheCacheManager ehCacheCacheManager) {
    CompositeCacheManager cacheManager = new CompositeCacheManager(redisCacheManager, ehCacheCacheManager);
    return cacheManager;
}

由于當前 Spring Context 中存在多個實現了 CacheManager.class 的 Bean,需要使用 @Primary 注解指定優先選擇的 CacheManager

不然CacheAspectSupport.class 會拋出No CacheResolver specified, and no unique bean of type CacheManager found. Mark one as primary (or give it the name 'cacheManager') or declare a specific CacheManager to use, that serves as the default one. 的錯誤。

業務代碼的配置

此時在業務代碼中就不需要配置 CacheManager:

@Service
@CacheConfig
public class UserServiceImpl implements UserService {
    private final static Logger LOG = LoggerFactory.getLogger(UserServiceImpl.class);

    @Override
    @CachePut(cacheNames = "user", key = "#user.id.toString()")
    public User save(User user) {
        LOG.info("saved user {}", user);
        return user;
    }

    @Override
    @CacheEvict(cacheNames = "user")
    public void delete(int id) {
        LOG.info("delete {}", id);
    }

    @Override
    @Cacheable(cacheNames = "user")
    public User get(int id) {
        LOG.info("get user {}", id);
        return new User(1, "zhang");
    }
}

源代碼

本工程源代碼可以從github 獲取。源代碼

總結

本文介紹了 Spring Cache 配置多種不同的 CacheManager 的具體實現方案,在實際生產中可以直接使用。 在使用 Spring Boot 的自動裝配時,我們一定要搞清楚其底層的配置原理。遇到默認的配置不能滿足時,就需要我們閱讀文檔和源代碼來進行解決了。
本文沒有涉及到 Spring Cache 的具體使用,相關的內容會在后續推出。

參考

  1. Spring Reference
  2. Spring Boot Reference
  3. Ehcache
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內容

  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,912評論 6 342
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,818評論 18 139
  • 一、簡介 Ehcache是一個用Java實現的使用簡單,高速,實現線程安全的緩存管理類庫,ehcache提供了用內...
    小程故事多閱讀 44,020評論 9 59
  • 周六南潯古鎮游,周日龍山綠道行走、打牌,你報名參加哪一天? 三月的活動,終于在這個周末開啟。 話說,我可以兩天都參...
    藍蝶landie閱讀 553評論 3 7
  • 夜,好靜謐,柔和的月光灑了一地銀白,夜,好深沉,父親那時起時落的鼾聲猶如一首動人的月光曲回蕩在夜色上空,望著熟睡...
    忐忑的魂魄閱讀 605評論 0 0