Spring Boot 集成 Redis Cache 和 EhCache Cache

原創(chuàng)-轉(zhuǎn)載請注明 http://tramp.cincout.cn/2017/10/31/spring-boot-2017-10-31-spring-boot-multi-cache-manager/

摘要

Spring Cache 為企業(yè)級級應用提供了類似于 Spring Transactional 的聲明式緩存抽象層。Spring 采用 AOP 的方式實現(xiàn)對多種底層緩存技術(shù)的適配。包括 REDIS碟狞、COUCHBASE力奋、EHCACHE 等矫夯。
當配置好 spring.cache.type=REDIS 時秘狞, Spring Boot 的自動裝配策略會自動的為我們配置好需要的底層緩存框架以及對應的CacheManager忍疾。這在大多數(shù)場景下是滿足需求的。
筆者在實際開發(fā)中遇到的場景是一些常量信息需要存儲到Redis中峰髓,利用Redis 的分布式緩存功能,使得多個節(jié)點可以共享該數(shù)據(jù)息尺;一些需要基于特定的緩存清理規(guī)則(采用LRU存儲最近最常使用的10000 條)的數(shù)據(jù)則需要采用EhCache 實現(xiàn)携兵。

原理

Spring Boot 的自動裝配

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

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

實現(xiàn)

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 數(shù)據(jù)源的相關(guān)信息:

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 配置文件的內(nèi)容:

<?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望抽。

業(yè)務(wù)中使用

在業(yè)務(wù)代碼中使用時為了區(qū)分不同的業(yè)務(wù)使用不同的CacheManager 有兩種方式實現(xiàn)。

  • 在業(yè)務(wù)代碼中采用@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 屬性)來對緩存進行區(qū)分谐鼎。Spring Cache 提供了CompositeCacheManager 來對所有的 CacheManager 進行代理舰蟆。
    根據(jù)指定的cacheName 去遍歷所有的 CacheManager,查找對應的緩存狸棍。

RedisCacheManager指定CacheNames

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

同時需要在緩存相關(guān)的注解上配置 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 中存在多個實現(xiàn)了 CacheManager.class 的 Bean,需要使用 @Primary 注解指定優(yōu)先選擇的 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. 的錯誤塌鸯。

業(yè)務(wù)代碼的配置

此時在業(yè)務(wù)代碼中就不需要配置 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 獲取。源代碼

總結(jié)

本文介紹了 Spring Cache 配置多種不同的 CacheManager 的具體實現(xiàn)方案唐片,在實際生產(chǎn)中可以直接使用丙猬。 在使用 Spring Boot 的自動裝配時,我們一定要搞清楚其底層的配置原理费韭。遇到默認的配置不能滿足時茧球,就需要我們閱讀文檔和源代碼來進行解決了。
本文沒有涉及到 Spring Cache 的具體使用星持,相關(guān)的內(nèi)容會在后續(xù)推出抢埋。

參考

  1. Spring Reference
  2. Spring Boot Reference
  3. Ehcache
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市督暂,隨后出現(xiàn)的幾起案子揪垄,更是在濱河造成了極大的恐慌,老刑警劉巖逻翁,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饥努,死亡現(xiàn)場離奇詭異,居然都是意外死亡八回,警方通過查閱死者的電腦和手機酷愧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辽社,“玉大人伟墙,你說我怎么就攤上這事〉吻Γ” “怎么了戳葵?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長汉匙。 經(jīng)常有香客問我拱烁,道長生蚁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任戏自,我火速辦了婚禮邦投,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘擅笔。我一直安慰自己志衣,他們只是感情好,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布猛们。 她就那樣靜靜地躺著念脯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弯淘。 梳的紋絲不亂的頭發(fā)上绿店,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音庐橙,去河邊找鬼假勿。 笑死,一個胖子當著我的面吹牛态鳖,可吹牛的內(nèi)容都是我干的转培。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼浆竭,長吁一口氣:“原來是場噩夢啊……” “哼堡距!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起兆蕉,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缤沦,沒想到半個月后虎韵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡缸废,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年包蓝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片企量。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡测萎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出届巩,到底是詐尸還是另有隱情硅瞧,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布恕汇,位于F島的核電站腕唧,受9級特大地震影響或辖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜枣接,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一颂暇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧但惶,春花似錦耳鸯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至妓肢,卻和暖如春捌省,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碉钠。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工纲缓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喊废。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓祝高,卻偏偏與公主長得像,于是被迫代替她去往敵國和親污筷。 傳聞我的和親對象是個殘疾皇子工闺,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)瓣蛀,斷路器陆蟆,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • 一、簡介 Ehcache是一個用Java實現(xiàn)的使用簡單惋增,高速叠殷,實現(xiàn)線程安全的緩存管理類庫,ehcache提供了用內(nèi)...
    小程故事多閱讀 43,823評論 9 59
  • 周六南潯古鎮(zhèn)游,周日龍山綠道行走稽亏、打牌壶冒,你報名參加哪一天? 三月的活動截歉,終于在這個周末開啟胖腾。 話說,我可以兩天都參...
    藍蝶landie閱讀 533評論 3 7
  • 夜,好靜謐胸嘁,柔和的月光灑了一地銀白瓶摆,夜,好深沉性宏,父親那時起時落的鼾聲猶如一首動人的月光曲回蕩在夜色上空群井,望著熟睡...
    忐忑的魂魄閱讀 576評論 0 0