spring boot spring cache實現多級緩衝, 只是按照自己的思想實現,若有讀者有更好的解決思路,歡迎指點
spring cache實現多級緩衝的思路如下:
添加自訂的CacheManager,自訂的Cache,在Cache裡實現多級緩衝的操作(增刪查)
配置類如下注意紅色標記的部分:
package com.zyc.zspringboot.config;import com.zyc.zspringboot.cache.MyCacheManager;import com.zyc.zspringboot.cache.MyCacheTemplate;import com.zyc.zspringboot.cache.MyRedisCache;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.cache.CacheManager;import org.springframework.cache.annotation.EnableCaching;import org.springframework.cache.ehcache.EhCacheCacheManager;import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;import org.springframework.context.annotation.AdviceMode;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.core.io.ClassPathResource;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.JdkSerializationRedisSerializer;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import redis.clients.jedis.JedisPoolConfig;import java.util.ArrayList;import java.util.List;/** * ClassName: RedisConfig * * @author zyc-admin * @date 2018年1月23日 * @Description: */@Configuration@EnableCaching(mode = AdviceMode.PROXY)// model屬性預設proxy// mode屬性,可以選擇值proxy和aspectj。預設使用proxy。當mode為proxy時,// 只有緩衝方法在外部被調用的時候才會生效。這也就意味著如果一個緩衝方法在一個對// 象的內部被調用SpringCache是不會發生作用的。而mode為aspectj時,就不會有// 這種問題了。另外使用proxy的時候,只有public方法上的@Cacheable才會發生作用。// 如果想非public上的方法也可以使用那麼就把mode改成aspectj。@ConfigurationProperties(prefix = "spring.redis")// 使用@ConfigurationProperties 需要實現屬性的getter setter方法,// 1.5之前版本需要在啟動類上使用@EnableConfigurationProperties進行啟用,1.5之後直接在配置類上使用@Component,// 由於本類使用@Configuration註解包含了@Component就不用在聲明@Component,// 這樣就可以直接在其他類中使用@Autowired直接把此類注入進來// extends CachingConfigurerSupportpublic class RedisConfig {private String hostName;private int port;private int timeOut;private int maxIdle;// 最大空閑串連數, 預設8個private int maxWaitMillis;// 擷取串連時的最大等待毫秒數private boolean testOnBorrow;// 在擷取串連的時候檢查有效性, 預設falseprivate boolean testWhileIdle;// 空閑是否檢查是否有效,預設為falsepublic String getHostName() {return hostName;}public void setHostName(String hostName) {this.hostName = hostName;}public int getPort() {return port;}public void setPort(int port) {this.port = port;}public int getTimeOut() {return timeOut;}public void setTimeOut(int timeOut) {this.timeOut = timeOut;}public int getMaxIdle() {return maxIdle;}public void setMaxIdle(int maxIdle) {this.maxIdle = maxIdle;}public int getMaxWaitMillis() {return maxWaitMillis;}public void setMaxWaitMillis(int maxWaitMillis) {this.maxWaitMillis = maxWaitMillis;}public boolean isTestOnBorrow() {return testOnBorrow;}public void setTestOnBorrow(boolean testOnBorrow) {this.testOnBorrow = testOnBorrow;}public boolean isTestWhileIdle() {return testWhileIdle;}public void setTestWhileIdle(boolean testWhileIdle) {this.testWhileIdle = testWhileIdle;}@Bean("jedisPoolConfig")public JedisPoolConfig jedisPoolConfig() {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxIdle(maxIdle);jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);jedisPoolConfig.setTestOnBorrow(true);jedisPoolConfig.setTestWhileIdle(false);return jedisPoolConfig;}@Beanpublic JedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) {// 如果叢集使用new JedisConnectionFactory(new// RedisClusterConfiguration()),叢集配置在RedisClusterConfiguration,這裡省略具體配置JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();redisConnectionFactory.setPoolConfig(jedisPoolConfig);redisConnectionFactory.setHostName(hostName);redisConnectionFactory.setPort(port);redisConnectionFactory.setTimeout(timeOut);return redisConnectionFactory;}/** * RedisTemplate配置 * * @param redisConnectionFactory * @return RedisTemplate */@Beanpublic RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);RedisSerializer<String> redisSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(redisSerializer);// Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new// Jackson2JsonRedisSerializer<Object>(// Object.class);// ObjectMapper om = new ObjectMapper();// om.setVisibility(PropertyAccessor.ALL,// JsonAutoDetect.Visibility.ANY);// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);// jackson2JsonRedisSerializer.setObjectMapper(om);// redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();redisTemplate.setValueSerializer(jdkSerializationRedisSerializer);return redisTemplate;}/** * redis緩衝管理器 * @param redisTemplate * @return */@Beanpublic RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate) {RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);// Number of seconds before expiration. Defaults to unlimited (0)cacheManager.setDefaultExpiration(120); //設定key-value逾時時間List<String> cacheNames = new ArrayList<>();cacheNames.add("myRedis");cacheNames.add("j2CacheRedis");cacheManager.setCacheNames(cacheNames);return cacheManager;}/** * spring cache整合(EhCache,Redis)二級緩衝具體Cache * @param redisCacheManager * @param redisTemplate * @return */@Beanpublic MyCacheTemplate myCacheTemplate(RedisCacheManager redisCacheManager,RedisTemplate<String, Object> redisTemplate){MyCacheTemplate myCacheTemplate=new MyCacheTemplate();myCacheTemplate.setRedisCacheManager(redisCacheManager);myCacheTemplate.setRedisTemplate(redisTemplate);myCacheTemplate.setName("j2CacheRedis");return myCacheTemplate;}/** * 自訂redis緩衝 * @param redisCacheManager * @param redisTemplate * @return */@Beanpublic MyRedisCache myRedisCache(RedisCacheManager redisCacheManager,RedisTemplate<String,Object> redisTemplate){MyRedisCache myRedisCache=new MyRedisCache();//自訂屬性配置緩衝名稱myRedisCache.setName("myRedis");//redis緩衝管理器myRedisCache.setRedisCacheManager(redisCacheManager);//redisTemplate 執行個體myRedisCache.setRedisTemplate(redisTemplate);return myRedisCache;}/** * spring cache 統一緩衝管理器 * @param myCacheTemplate * @param myRedisCache * @return */@Bean@Primarypublic CacheManager cacheManager(MyCacheTemplate myCacheTemplate,MyRedisCache myRedisCache){MyCacheManager cacheManager=new MyCacheManager();cacheManager.setMyCacheTemplate(myCacheTemplate);cacheManager.setMyRedisCache(myRedisCache);return cacheManager;} // 整合ehcache @Bean public EhCacheCacheManager ehCacheCacheManager() { EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager(ehCacheManagerFactoryBean().getObject()); return ehCacheCacheManager; } @Bean public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() { EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean(); //這裡暫時借用shiro的ehcache設定檔 Resource r=new ClassPathResource("ehcache-shiro.xml"); cacheManagerFactoryBean.setConfigLocation(r); cacheManagerFactoryBean.setShared(true); return cacheManagerFactoryBean; }}
自訂CacheManager如下:
package com.zyc.zspringboot.cache;import org.springframework.cache.Cache;import org.springframework.cache.CacheManager;import java.util.Collection;/** * @author zyc-admin * @data 2018-03-20 10:12 **/public class MyCacheManager implements CacheManager {private MyCacheTemplate myCacheTemplate;private MyRedisCache myRedisCache;public MyRedisCache getMyRedisCache() {return myRedisCache;}public void setMyRedisCache(MyRedisCache myRedisCache) {this.myRedisCache = myRedisCache;}public MyCacheTemplate getMyCacheTemplate() {return myCacheTemplate;}public void setMyCacheTemplate(MyCacheTemplate myCacheTemplate) {this.myCacheTemplate = myCacheTemplate;}@Overridepublic Cache getCache(String name) {//多級緩衝實現if(name.equals(myCacheTemplate.getName())){return myCacheTemplate;}return null;}@Overridepublic Collection<String> getCacheNames() {return null;}}
自訂Cache實現:
package com.zyc.zspringboot.cache;import net.sf.ehcache.CacheManager;import net.sf.ehcache.Element;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.cache.Cache;import org.springframework.cache.support.SimpleValueWrapper;import org.springframework.data.redis.cache.RedisCacheElement;import org.springframework.data.redis.cache.RedisCacheKey;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.core.RedisTemplate;import java.util.concurrent.Callable;/** * @author zyc-admin * @data 2018-03-19 17:15 **/public class MyCacheTemplate implements Cache {private static final Logger logger= LoggerFactory.getLogger(MyCacheTemplate.class);private CacheManager ehCacheManager;private RedisCacheManager redisCacheManager;private RedisTemplate<String, Object> redisTemplate;public CacheManager getEhCacheManager() {return ehCacheManager;}public void setEhCacheManager(CacheManager ehCacheManager) {this.ehCacheManager = ehCacheManager;}public RedisCacheManager getRedisCacheManager() {return redisCacheManager;}public void setRedisCacheManager(RedisCacheManager redisCacheManager) {this.redisCacheManager = redisCacheManager;}public RedisTemplate<String, Object> getRedisTemplate() {return redisTemplate;}public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}private String name;@Overridepublic String getName() {return name;} //自己添加set方法,實現Cache本身無此方法public void setName(String name){this.name=name;}@Overridepublic Object getNativeCache() {return null;}@Overridepublic ValueWrapper get(Object key) {ehCacheManager=CacheManager.getCacheManager("ec");if(ehCacheManager!=null){net.sf.ehcache.Cache myEhcache = ehCacheManager.getCache(getName());logger.info("取資料ehcache庫===key:{}",key);if(myEhcache.get(key)!=null){ValueWrapper v=new SimpleValueWrapper(myEhcache.get(key).getObjectValue());return v;}}Cache myRedis = redisCacheManager.getCache(getName());if(myRedis!=null){logger.info("取資料reids庫===key:{}",key);if(myRedis.get(key)!=null){RedisCacheElement vr=new RedisCacheElement(new RedisCacheKey(key),myRedis.get(key).get());return vr;}}return null;}@Overridepublic <T> T get(Object key, Class<T> type) {System.out.println(key+"======================="+type);return null;}@Overridepublic <T> T get(Object key, Callable<T> valueLoader) {return null;}@Overridepublic void put(Object key, Object value) {ehCacheManager=CacheManager.getCacheManager("ec");if(ehCacheManager!=null){net.sf.ehcache.Cache myEhcache = ehCacheManager.getCache(getName());Element e=new Element(key,value);logger.info("插入ehcache庫===key:{},value:{}",key,value);myEhcache.put(e);}Cache myRedis = redisCacheManager.getCache(getName());if(myRedis!=null){logger.info("插入reids庫===key:{},value:{}",key,value);myRedis.put(key,value);}System.out.println("cha ru key "+ key);}@Overridepublic ValueWrapper putIfAbsent(Object key, Object value) {return null;}@Overridepublic void evict(Object key) {Cache myRedis = redisCacheManager.getCache(getName());if(myRedis!=null){logger.info("刪除reids庫===key:{}",key);myRedis.evict(key);}ehCacheManager=CacheManager.getCacheManager("ec");if(ehCacheManager!=null){net.sf.ehcache.Cache myEhcache = ehCacheManager.getCache(getName());logger.info("刪除ehcache庫===key:{}",key);if(myEhcache.isKeyInCache(key)){myEhcache.remove(key);}}System.out.println("刪除 key "+ key);}@Overridepublic void clear() {Cache myRedis = redisCacheManager.getCache(getName());myRedis.clear();ehCacheManager=CacheManager.getCacheManager("ec");if(ehCacheManager!=null) {net.sf.ehcache.Cache myEhcache = ehCacheManager.getCache(getName());myEhcache.removeAll();}}}
使用spring cache 註解使用緩衝如下:(在首次調用這個方法時,自訂緩衝的put,get方法總是列印2次插入資料,2次取資料,暫時不知道什麼原因,若有哪位大神知道,歡迎評論指點。)
@Cacheable(value = "j2CacheRedis", key = "'role:id:'+#id",unless ="#result == null")//@Log(value = "擷取資料並存入緩衝")public Role getRole(String id) {// TODO Auto-generated method stubreturn roleDao.getRole(id);}
application.properties設定檔如下
#redis ------start-------spring.redis.hostName=127.0.0.1spring.redis.port=63791spring.redis.timeOut=1000spring.redis.maxIdle=10spring.redis.maxWaitMillis=15000spring.redis.testOnBorrow=truespring.redis.testWhileIdle=false
ecache-shiro.xml設定檔如下(因本項目使用了shiro,所以Ehcache使用shiro的緩衝設定檔)
<?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" name="ec"><diskStore path="java.io.tmpdir"/> <!-- 緩衝存放目錄(此目錄為放入系統預設緩衝目錄),也可以是”D:/cache“ java.io.tmpdir --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> <!-- --> <cache name="j2CacheRedis" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="140" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> <!-- name:Cache的唯一標識 maxElementsInMemory:記憶體中最大緩衝對象數 maxElementsOnDisk:磁碟中最大緩衝對象數,若是0表示無窮大 eternal:Element是否永久有效,一但設定了,timeout將不起作用 overflowToDisk:配置此屬性,當記憶體中Element數量達到maxElementsInMemory時,Ehcache將會Element寫到磁碟中 timeToIdleSeconds:設定Element在失效前的允許閑置時間。僅當element不是永久有效時使用,可選屬性,預設值是0,也就是可閑置時間無窮大 timeToLiveSeconds:設定Element在失效前允許存活時間。最大時間介於建立時間和失效時間之間。僅當element不是永久有效時使用,預設是0.,也就是element存活時間無窮大 diskPersistent:是否緩衝虛擬機器重啟期資料 diskExpiryThreadIntervalSeconds:磁碟失效線程已耗用時間間隔,預設是120秒 diskSpoolBufferSizeMB:這個參數設定DiskStore(磁碟緩衝)的緩衝區大小。預設是30MB。每個Cache都應該有自己的一個緩衝區 memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用) --></ehcache>
整合過程遇到的問題:
1 建立cacheManager bean時失敗
解決方案:可能你配置了多個cacheManager,確保不重名,在自訂的那個cacheManager上添加 @Primary註解
自訂的CacheManager 要實現CacheManager介面,
注意不要繼承org.springframework.cache.support.AbstractCacheManager這個類來實現自己CacheManager
2 建立EhCacheCacheManager失敗
解決方案:查看你的EhCacheCacheManager是否和shiro的EhCache緩衝有衝突,
EhCache 2.5版本之上,jvm裡面一般只能存在EhCache執行個體。
3 內部方法調用帶有spring cache註解的方法 緩衝失效
解決方案:spring cache 依賴於aop,使用AopContext.currentProxy().你的方法,
使用之前需先暴露代理對象添加註解 @EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)
exposeProxy:暴露代理對象,proxyTargetClass強制使用cglib代理