SpringBoot_Cache自定义使用SimpleCacheManager方式

 更新时间:2023年07月27日 10:59:55   作者:恐龙弟旺仔  
这篇文章主要介绍了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();
		}
	}
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java序列化原理详解

    Java序列化原理详解

    这篇文章主要介绍了Java序列化原理详解,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-06-06
  • TransmittableThreadLocal通过javaAgent实现线程传递并支持ForkJoin

    TransmittableThreadLocal通过javaAgent实现线程传递并支持ForkJoin

    这篇文章主要介绍了TransmittableThreadLocal通过javaAgent实现线程传递并支持ForkJoin详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • Java多线程编程安全退出线程方法介绍

    Java多线程编程安全退出线程方法介绍

    这篇文章主要介绍了Java多线程编程安全退出线程方法介绍,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • Java中的TreeSet集合详解

    Java中的TreeSet集合详解

    这篇文章主要介绍了Java中的TreeSet集合详解,TreeSet 是一个 有序集合,它扩展了 AbstractSet 类并实现了 NavigableSet 接口,作为自平衡二叉搜索树,二叉树的每个节点包括一个额外的位,用于识别红色或黑色的节点的颜色,需要的朋友可以参考下
    2023-09-09
  • Java基于NIO实现群聊系统

    Java基于NIO实现群聊系统

    这篇文章主要为大家详细介绍了Java基于NIO实现群聊系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • SpringBoot使用Micrometer实现度量和监控

    SpringBoot使用Micrometer实现度量和监控

    在构建和维护现代应用程序时,度量和监控是至关重要的,它们可以帮助您了解应用程序的性能、稳定性和可用性,本文将介绍如何在Spring Boot应用程序中使用Micrometer进行度量和监控,需要的朋友可以参考下
    2023-10-10
  • SpringBoot解决Required String parameter xxx is not present问题

    SpringBoot解决Required String parameter xxx is not prese

    这篇文章主要介绍了SpringBoot解决Required String parameter xxx is not present问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • java.util.Collection源码分析与深度理解

    java.util.Collection源码分析与深度理解

    这篇文章主要给大家介绍了关于java.util.Collection的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-03-03
  • spring集成okhttp3的步骤详解

    spring集成okhttp3的步骤详解

    okhttp是一个封装URL,比HttpClient更友好易用的工具,下面这篇文章主要给大家介绍了关于spring集成okhttp3的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2018-04-04
  • Java并发应用之任务执行分析

    Java并发应用之任务执行分析

    这篇文章主要为大家详细介绍了JavaJava并发应用编程中任务执行分析的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2023-07-07

最新评论