SpringBoot_Cache自定義使用SimpleCacheManager方式
SpringBoot_Cache自定義使用SimpleCacheManager
在SpringBoot_Cache中,會默認使用SimpleCacheManager。
但是筆者遇到一個問題,就是當前項目中也引用了redis的maven依賴,導致Cache使用了JedisManager,筆者還是想使用默認的SimpleCacheManager
這個時候就需要我們手動生成SimpleCacheManager的bean,則Cache會強制使用該bean
關(guān)于SpringBoot_Cache的使用筆者就不再贅述,讀者可參考網(wǎng)絡(luò)文章。
解決方案
創(chuàng)建一個Config類,然后主動生成SimpleCacheManager,代碼如下
@Configuration
public class CacheManagerConfig {
@Bean("cacheManager")
// List<Cache>會主動搜索Cache的實現(xiàn)bean,并添加到caches中
public SimpleCacheManager cacheManager(List<Cache> caches){
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(caches);
return simpleCacheManager;
}
@Bean("stockDetail")
public ConcurrentMapCacheFactoryBean stockDetail(){
ConcurrentMapCacheFactoryBean stockDetail = new ConcurrentMapCacheFactoryBean();
// 如果用戶設(shè)置名稱為stockDetail的緩存,則需要添加這樣的一個bean
stockDetail.setName("stockDetail");
return stockDetail;
}
@Bean("detailMsg")
public ConcurrentMapCacheFactoryBean detailMsg(){
ConcurrentMapCacheFactoryBean stockDetail = new ConcurrentMapCacheFactoryBean();
// 如果用戶設(shè)置名稱為detailMsg的緩存,則需要添加這樣的一個bean
stockDetail.setName("detailMsg");
return stockDetail;
}
}注意:如果讀者在使用@Cache的時候,需要多個不同命名的cache時,需添加多個ConcurrentMapCacheFactoryBean
spring cache自定義spring cacheManager和cache
緩存 可以提高訪問速度,對高性能、高并發(fā)有一定的治療效果。
緩存從存儲位置可以分為 JVM級別緩存,進程級別緩存。JVM級別緩存優(yōu)點:訪問速度最快;缺點:不能實現(xiàn)分布式緩存,因為每個節(jié)點都是一個單獨的JVM實例,所以各個節(jié)點的緩存不可見,會導致緩存不一致。進程級別緩存優(yōu)點:保證緩存一致性,因為進程級別的緩存 相當于一個緩存服務(wù)器,各個節(jié)點都去同一個緩存服務(wù)器取數(shù)據(jù),所以緩存一致性有保證。進程級別緩存缺點:IO消耗,相對慢于JVM級別緩存,但還是比數(shù)據(jù)庫查詢快。
Spring 對 cache 進行了抽象 并提供注解。但沒有具體實現(xiàn)緩存。類似于JDBC接口的意思。定義了規(guī)范,讓小面的廠商進行具體的實現(xiàn)。其中定義了兩個主要的接口:緩存管理器接口:CacheManager 緩存接口:Cache
緩存管理器接口:CacheManager
接口很簡單 只有兩個方法:
public interface CacheManager {
@Nullable
Cache getCache(String name);
Collection<String> getCacheNames();
}Cache getCache(String name);
此方法可以定義根據(jù)緩存名稱獲取緩存,此方法可以實現(xiàn)二級緩存。
Collection<String> getCacheNames();
此方法獲取所有緩存名稱。
cacheManager是spring獲取cache的入口,所以你可以在getCache(String name) 方法中定義你的獲取cache的實現(xiàn)邏輯。
public class MyCacheManager implements CacheManager {
private ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
public MyCacheManager(String name, Cache cache) {
this.caches.put(name, cache);
}
@Override
public Cache getCache(String name) {
//更加緩存名字獲取緩存
Cache cache = this.caches.get(name);
if (cache != null) {
return cache;
} else {
//如果指定的緩存名字沒有找到 則新建一個ConcurrentMapCache 并保存在cachemanager中
ConcurrentMapCache newCache = new ConcurrentMapCache(name);
caches.put(name, newCache);
return newCache;
}
}
@Override
public Collection<String> getCacheNames() {
return this.caches.keySet();
}
}自定義緩存實現(xiàn)
spring只是定義了緩存 并實現(xiàn)了幾個常用的cache比如目前較為流行的緩存有conCurrentMapCache、ehcache、caffeine。
而redis的實現(xiàn)由redis廠商提供。
其中conCurrentMapCache由spring實現(xiàn),在spring-context包中。ehcache、caffeine的實現(xiàn)放在了spring-context-support包下。
rediscache實現(xiàn)在 spring-data-redis包下


定義好cacheManager后將其加入spring容器管理。下面貼出緩存在spring配置文件中的配置有些bean是在接下來講的內(nèi)容。
<bean id="cacheManager" class="com.app.utils.cache.MyCacheManager"> <constructor-arg value="userCache" /> <!-- 緩存名稱 --> <constructor-arg ref="mapCaffeCache" /> <!-- 緩存實現(xiàn)類 --> </bean> <!-- 自定義緩存實現(xiàn) --> <bean id="mapCaffeCache" class="com.app.utils.cache.MapCaffeCache"></bean> <!-- 自定義緩存key實現(xiàn) --> <bean id="cacheKeyGenerator" class="com.app.utils.cache.CacheKeyGenerator"></bean> <!-- 自定義spring啟動加載bean時加載緩存 --> <bean id="cacheBeanPostProcessor" class="com.app.CacheBeanPostProcessor"></bean>
配置好bean后 在cache注解中 都會有cacheManager的指定 比如@cacheable注解 就有cacheManager的指定
@Cacheable(cacheManager = "cacheManager", key = "#i", value = "userCache", condition = "#i>7", unless = "#i>8")
public String getList(int i) {
return "list server method :: "
+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}cache接口
public interface Cache {
/**
* Return the cache name.
*/
String getName();
/**
* Return the underlying native cache provider.
*/
Object getNativeCache();
//獲取緩存的主要方法
@Nullable
ValueWrapper get(Object key);
@Nullable
<T> T get(Object key, @Nullable Class<T> type);
@Nullable
<T> T get(Object key, Callable<T> valueLoader);
//添加緩存的主要方法
void put(Object key, @Nullable Object value);
@Nullable
default ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
ValueWrapper existingValue = get(key);
if (existingValue == null) {
put(key, value);
}
return existingValue;
}
void evict(Object key);
default boolean evictIfPresent(Object key) {
evict(key);
return false;
}
void clear();
default boolean invalidate() {
clear();
return false;
}
@FunctionalInterface
interface ValueWrapper {
@Nullable
Object get();
}
@SuppressWarnings("serial")
class ValueRetrievalException extends RuntimeException {
@Nullable
private final Object key;
public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) {
super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
this.key = key;
}
@Nullable
public Object getKey() {
return this.key;
}
}
}cache的自定義實現(xiàn)類
public class MapCaffeCache implements Cache {
// private ConcurrentMapCache mapCache = new ConcurrentMapCache("mapCache");
private com.github.benmanes.caffeine.cache.@NonNull Cache<Object, Object> mapCache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS).expireAfterAccess(5, TimeUnit.SECONDS).maximumSize(5).build();
private com.github.benmanes.caffeine.cache.@NonNull Cache<Object, Object> caffeCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES).expireAfterAccess(1, TimeUnit.MINUTES).maximumSize(100).build();
@Autowired
private StringRedisTemplate redisTemplate;
private String name = "userCache";
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return this;
}
@Override
public ValueWrapper get(Object key) {
@Nullable
Object ob = mapCache.getIfPresent(key);
// 如果一級緩存有數(shù)據(jù) 直接返回 不觸發(fā)二級緩存
if (ob != null) {
System.out.println(String.format("Cache L1 (CaffeineCache) :: %s = %s", key, ob));
SimpleValueWrapper valueWrapper = new SimpleValueWrapper(ob);
return valueWrapper;
}
Object obj = caffeCache.getIfPresent(key);
if (obj != null) {
SimpleValueWrapper valueWrapper2 = new SimpleValueWrapper(obj);
System.out.println(String.format("Cache L2 (CaffeineCache) :: %s = %s", key, obj));
// 如果二級緩存有數(shù)據(jù) 則更新到一級緩存
mapCache.put(key, obj);
return valueWrapper2;
}
return null;
}
@Override
public <T> T get(Object key, Class<T> type) {
return (T) get(key).get();
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
return (T) get(key).get();
}
@Override
public void put(Object key, Object value) {
mapCache.put(key, value);
caffeCache.put(key, value);
//當nginx搭建集群時 用redis 訂閱/發(fā)布 功能同步各項目點的緩存一致性
redisTemplate.convertAndSend("ch1", key + ":>" + value);
}
@Override
public void evict(Object key) {
mapCache.asMap().remove(key);
caffeCache.asMap().remove(key);
}
@Override
public void clear() {
mapCache.asMap().clear();
caffeCache.asMap().clear();
}
}當cacheManager和cache實現(xiàn)完成后就是具體的cache注解使用了。主要有@Cacheable @Cacheput @CacheEvict @Caching @CacheConfig。主要講下@cacheable 其他注解設(shè)置都基本差不多。
@Cacheable用于獲取緩存數(shù)據(jù),如果緩存沒有命中則執(zhí)行被注解的方法 一般是執(zhí)行數(shù)據(jù)庫查詢,并剛數(shù)據(jù)庫查詢結(jié)果放入緩存中,cacheable緩存注解主要有一下設(shè)置:
@Cacheable(cacheManager = "cacheManager", key = "#i", value = "userCache", condition = "#i>7", unless = "#i>8")
- cacheManager :設(shè)置緩存管理器 相當于數(shù)據(jù)庫中的庫名稱
- value : 設(shè)置緩存名稱 相當于數(shù)據(jù)庫中的表
- key:設(shè)置緩存鍵名稱 緩存是一個key value存儲方式 所以可以是查找用的
- condition :設(shè)置滿足條件 true則緩存 false不緩存
- unless:設(shè)置不緩存條件 true不緩存 false 緩存
key的設(shè)置規(guī)則
- key = " 'key-name' " 寫死一個key 即 key-name 就是key的名字。注意要用單引號
- key="#id" 使用注解的方法中的參數(shù) id 的值作為key ,id 是方法參數(shù)列表的名字
- key="cacheKeyGenerator" cacheKeyGenerator為spring容器管理的自定義key生成器
自定義key生成器
實現(xiàn)KeyGenerator接口就可以自定義一個key生成方法
public class CacheKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
if (params.length == 0) {
return "spring:cache:defaultKey";
} else {
return target.getClass().getSimpleName() + ":" + method.getName();
}
}
}總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java多線程批量拆分List導入數(shù)據(jù)庫的實現(xiàn)過程
這篇文章主要給大家介紹了關(guān)于java多線程批量拆分List導入數(shù)據(jù)庫的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2021-10-10
springmvc前臺向后臺傳值幾種方式總結(jié)(從簡單到復雜)
今天小編就為大家分享一篇springmvc前臺向后臺傳值幾種方式總結(jié)(從簡單到復雜),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
java計算任意位水仙花數(shù)示例(回文數(shù))
這篇文章主要介紹了java計算任意位水仙花數(shù)示例(回文數(shù)),需要的朋友可以參考下2014-05-05

