SpringBoot_Cache自定义使用SimpleCacheManager方式
SpringBoot_Cache自定义使用SimpleCacheManager
在SpringBoot_Cache中,会默认使用SimpleCacheManager。
但是笔者遇到一个问题,就是当前项目中也引用了redis的maven依赖,导致Cache使用了JedisManager,笔者还是想使用默认的SimpleCacheManager
这个时候就需要我们手动生成SimpleCacheManager的bean,则Cache会强制使用该bean
关于SpringBoot_Cache的使用笔者就不再赘述,读者可参考网络文章。
解决方案
创建一个Config类,然后主动生成SimpleCacheManager,代码如下
@Configuration public class CacheManagerConfig { @Bean("cacheManager") // List<Cache>会主动搜索Cache的实现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(); // 如果用户设置名称为stockDetail的缓存,则需要添加这样的一个bean stockDetail.setName("stockDetail"); return stockDetail; } @Bean("detailMsg") public ConcurrentMapCacheFactoryBean detailMsg(){ ConcurrentMapCacheFactoryBean stockDetail = new ConcurrentMapCacheFactoryBean(); // 如果用户设置名称为detailMsg的缓存,则需要添加这样的一个bean stockDetail.setName("detailMsg"); return stockDetail; } }
注意:如果读者在使用@Cache的时候,需要多个不同命名的cache时,需添加多个ConcurrentMapCacheFactoryBean
spring cache自定义spring cacheManager和cache
缓存 可以提高访问速度,对高性能、高并发有一定的治疗效果。
缓存从存储位置可以分为 JVM级别缓存,进程级别缓存。JVM级别缓存优点:访问速度最快;缺点:不能实现分布式缓存,因为每个节点都是一个单独的JVM实例,所以各个节点的缓存不可见,会导致缓存不一致。进程级别缓存优点:保证缓存一致性,因为进程级别的缓存 相当于一个缓存服务器,各个节点都去同一个缓存服务器取数据,所以缓存一致性有保证。进程级别缓存缺点:IO消耗,相对慢于JVM级别缓存,但还是比数据库查询快。
Spring 对 cache 进行了抽象 并提供注解。但没有具体实现缓存。类似于JDBC接口的意思。定义了规范,让小面的厂商进行具体的实现。其中定义了两个主要的接口:缓存管理器接口:CacheManager 缓存接口:Cache
缓存管理器接口:CacheManager
接口很简单 只有两个方法:
public interface CacheManager { @Nullable Cache getCache(String name); Collection<String> getCacheNames(); }
Cache getCache(String name);
此方法可以定义根据缓存名称获取缓存,此方法可以实现二级缓存。
Collection<String> getCacheNames();
此方法获取所有缓存名称。
cacheManager是spring获取cache的入口,所以你可以在getCache(String name) 方法中定义你的获取cache的实现逻辑。
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(); } }
自定义缓存实现
spring只是定义了缓存 并实现了几个常用的cache比如目前较为流行的缓存有conCurrentMapCache、ehcache、caffeine。
而redis的实现由redis厂商提供。
其中conCurrentMapCache由spring实现,在spring-context包中。ehcache、caffeine的实现放在了spring-context-support包下。
rediscache实现在 spring-data-redis包下
定义好cacheManager后将其加入spring容器管理。下面贴出缓存在spring配置文件中的配置有些bean是在接下来讲的内容。
<bean id="cacheManager" class="com.app.utils.cache.MyCacheManager"> <constructor-arg value="userCache" /> <!-- 缓存名称 --> <constructor-arg ref="mapCaffeCache" /> <!-- 缓存实现类 --> </bean> <!-- 自定义缓存实现 --> <bean id="mapCaffeCache" class="com.app.utils.cache.MapCaffeCache"></bean> <!-- 自定义缓存key实现 --> <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的自定义实现类
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); // 如果一级缓存有数据 直接返回 不触发二级缓存 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)); // 如果二级缓存有数据 则更新到一级缓存 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 订阅/发布 功能同步各项目点的缓存一致性 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实现完成后就是具体的cache注解使用了。主要有@Cacheable @Cacheput @CacheEvict @Caching @CacheConfig。主要讲下@cacheable 其他注解设置都基本差不多。
@Cacheable用于获取缓存数据,如果缓存没有命中则执行被注解的方法 一般是执行数据库查询,并刚数据库查询结果放入缓存中,cacheable缓存注解主要有一下设置:
@Cacheable(cacheManager = "cacheManager", key = "#i", value = "userCache", condition = "#i>7", unless = "#i>8")
- cacheManager :设置缓存管理器 相当于数据库中的库名称
- value : 设置缓存名称 相当于数据库中的表
- key:设置缓存键名称 缓存是一个key value存储方式 所以可以是查找用的
- condition :设置满足条件 true则缓存 false不缓存
- unless:设置不缓存条件 true不缓存 false 缓存
key的设置规则
- key = " 'key-name' " 写死一个key 即 key-name 就是key的名字。注意要用单引号
- key="#id" 使用注解的方法中的参数 id 的值作为key ,id 是方法参数列表的名字
- key="cacheKeyGenerator" cacheKeyGenerator为spring容器管理的自定义key生成器
自定义key生成器
实现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(); } } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
TransmittableThreadLocal通过javaAgent实现线程传递并支持ForkJoin
这篇文章主要介绍了TransmittableThreadLocal通过javaAgent实现线程传递并支持ForkJoin详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-06-06SpringBoot解决Required String parameter xxx is not prese
这篇文章主要介绍了SpringBoot解决Required String parameter xxx is not present问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2022-01-01
最新评论