一、前言
大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
在实现本地缓存的时候,我们经常使用线程安全的ConcurrentHashMap来暂存数据,然后加上SpringBoot自带的@Scheduled定时刷新缓存。虽然这样可以实现本地缓存,但既不优雅也不安全,一个好的本地缓存工具应该是这样搭建的:
kotlin
DAL实现,产出DAO和DO对象,定义缓存领域模型
定义缓存名称,特别关注缓存的初始化顺序
编写数据仓库,通过模型转换器实现数据模型到缓存模型的转化
编写缓存管理器,推荐继承抽象管理器 {@link AbstractCacheManager}
根据业务需求,设计缓存数据接口(putAll,get,getCacheInfo等基础API)
完成bean配置,最好是可插拔的注册方式,缓存管理器和数据仓库、扩展点服务
二、我的思路
1. 每个处理器都有缓存名字、描述信息、缓存初始化顺序等信息,所以应该定义一个接口,名字为CacheNameDomain;
java
package com.example.test.localcache;
public interface CacheNameDomain {
/**
* 缓存初始化顺序,级别越低,越早被初始化
* <p>
* 如果缓存的加载存在一定的依赖关系,通过缓存级别控制初始化或者刷新时缓存数据的加载顺序<br>
* 级别越低,越早被初始化<br>
* <p>
* 如果缓存的加载没有依赖关系,可以使用默认顺序<code>Ordered.LOWEST_PRECEDENCE</code>
*
* @return 初始化顺序
* @see org.springframework.core.Ordered
*/
int getOrder();
/**
* 缓存名称,推荐使用英文大写字母表示
*
* @return 缓存名称
*/
String getName();
/**
* 缓存描述信息,用于打印日志
*
* @return 缓存描述信息
*/
String getDescription();
}
2. 每个处理器都有生命周期,如初始化、刷新、获取处理器信息等操作,这应该也是一个接口,处理器都应该声明这个接口,名字为CacheManager;
java
package com.example.test.localcache;
import org.springframework.cache.support.AbstractCacheManager;
import org.springframework.core.Ordered;
public interface CacheManager extends Ordered {
/**
* 初始化缓存
*/
public void initCache();
/**
* 刷新缓存
*/
public void refreshCache();
/**
* 获取缓存的名称
*
* @return 缓存名称
*/
public CacheNameDomain getCacheName();
/**
* 打印缓存信息
*/
public void dumpCache();
/**
* 获取缓存条数
*
* @return
*/
public long getCacheSize();
}
3. 定义一个缓存处理器生命周期的处理器,会声明CacheManager,做第一次的处理,也是所有处理器的父类,所以这应该是一个抽象类,名字为AbstractCacheManager;
java
package com.example.test.localcache;
import com.example.test.localcache.manager.CacheManagerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
/**
* @description 缓存管理抽象类,缓存管理器都要集成这个抽象类
*/
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
/**
* LOGGER
*/
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractCacheManager.class);
/**
* 获取可读性好的缓存信息,用于日志打印操作
*
* @return 缓存信息
*/
protected abstract String getCacheInfo();
/**
* 查询数据仓库,并加载到缓存数据
*/
protected abstract void loadingCache();
/**
* 查询缓存大小
*
* @return
*/
protected abstract long getSize();
/**
* @see InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
CacheManagerRegistry.register(this);
}
@Override
public void initCache() {
String description = getCacheName().getDescription();
LOGGER.info("start init {}", description);
loadingCache();
afterInitCache();
LOGGER.info("{} end init", description);
}
@Override
public void refreshCache() {
String description = getCacheName().getDescription();
LOGGER.info("start refresh {}", description);
loadingCache();
afterRefreshCache();
LOGGER.info("{} end refresh", description);
}
/**
* @see org.springframework.core.Ordered#getOrder()
*/
@Override
public int getOrder() {
return getCacheName().getOrder();
}
@Override
public void dumpCache() {
String description = getCacheName().getDescription();
LOGGER.info("start print {} {}{}", description, "\n", getCacheInfo());
LOGGER.info("{} end print", description);
}
/**
* 获取缓存条目
*
* @return
*/
@Override
public long getCacheSize() {
LOGGER.info("Cache Size Count: {}", getSize());
return getSize();
}
/**
* 刷新之后,其他业务处理,比如监听器的注册
*/
protected void afterInitCache() {
//有需要后续动作的缓存实现
}
/**
* 刷新之后,其他业务处理,比如缓存变通通知
*/
protected void afterRefreshCache() {
//有需要后续动作的缓存实现
}
}
4. 当有很多缓存处理器的时候,那么需要一个统一注册、统一管理的的地方,可以实现对分散在各处的缓存管理器统一维护,名字为CacheManagerRegistry;
java
package com.example.test.localcache.manager;
import com.example.test.localcache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.OrderComparator;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description 缓存管理器集中注册接口,可以实现对分散在各处的缓存管理器统一维护
*/
@Component
public final class CacheManagerRegistry implements InitializingBean {
/**
* LOGGER
*/
private static final Logger logger = LoggerFactory.getLogger(CacheManagerRegistry.class);
/**
* 缓存管理器
*/
private static Map<String, CacheManager> managerMap = new ConcurrentHashMap<String, CacheManager>();
/**
* 注册缓存管理器
*
* @param cacheManager 缓存管理器
*/
public static void register(CacheManager cacheManager) {
String cacheName = resolveCacheName(cacheManager.getCacheName().getName());
managerMap.put(cacheName, cacheManager);
}
/**
* 刷新特定的缓存
*
* @param cacheName 缓存名称
*/
public static void refreshCache(String cacheName) {
CacheManager cacheManager = managerMap.get(resolveCacheName(cacheName));
if (cacheManager == null) {
logger.warn("cache manager is not exist,cacheName=", cacheName);
return;
}
cacheManager.refreshCache();
cacheManager.dumpCache();
}
/**
* 获取缓存总条数
*/
public static long getCacheSize(String cacheName) {
CacheManager cacheManager = managerMap.get(resolveCacheName(cacheName));
if (cacheManager == null) {
logger.warn("cache manager is not exist,cacheName=", cacheName);
return 0;
}
return cacheManager.getCacheSize();
}
/**
* 获取缓存列表
*
* @return 缓存列表
*/
public static List<String> getCacheNameList() {
List<String> cacheNameList = new ArrayList<>();
managerMap.forEach((k, v) -> {
cacheNameList.add(k);
});
return cacheNameList;
}
public void startup() {
try {
deployCompletion();
} catch (Exception e) {
logger.error("Cache Component Init Fail:", e);
// 系统启动时出现异常,不希望启动应用
throw new RuntimeException("启动加载失败", e);
}
}
/**
* 部署完成,执行缓存初始化
*/
private void deployCompletion() {
List<CacheManager> managers = new ArrayList<CacheManager>(managerMap.values());
// 根据缓存级别进行排序,以此顺序进行缓存的初始化
Collections.sort(managers, new OrderComparator());
// 打印系统启动日志
logger.info("cache manager component extensions:");
for (CacheManager cacheManager : managers) {
String beanName = cacheManager.getClass().getSimpleName();
logger.info(cacheManager.getCacheName().getName(), "==>", beanName);
}
// 初始化缓存
for (CacheManager cacheManager : managers) {
cacheManager.initCache();
cacheManager.dumpCache();
}
}
/**
* 解析缓存名称,大小写不敏感,增强刷新的容错能力
*
* @param cacheName 缓存名称
* @return 转换大写的缓存名称
*/
private static String resolveCacheName(String cacheName) {
return cacheName.toUpperCase();
}
@Override
public void afterPropertiesSet() throws Exception {
startup();
}
}
5. 当处理器创建好之后,就需要加上定时刷新了,可以利用上面的CacheManagerRegistry来实现
java
package com.example.test.localcache.manager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.text.MessageFormat;
import java.util.List;
/**
* @description 定时、并按Order顺序刷新缓存
*/
@Component
public class CacheManagerTrigger {
/**
* logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(CacheManagerTrigger.class);
/**
* 触发刷新缓存
*/
@Scheduled(fixedRate = 1000 * 60, initialDelay = 1000 * 60)
private static void refreshCache() {
List<String> cacheNameList = getCacheList();
LOGGER.info("start refresh instruction ,cacheNameList={}", cacheNameList);
if (CollectionUtils.isEmpty(cacheNameList)) {
LOGGER.warn("cache name list are empty");
return;
}
long totalCacheSize = 0;
for (String cacheName : cacheNameList) {
CacheManagerRegistry.refreshCache(cacheName);
totalCacheSize += CacheManagerRegistry.getCacheSize(cacheName);
}
LOGGER.info(MessageFormat.format("缓存刷新成功,缓存管理器:{0}个,总缓存条目数量:{1}条", cacheNameList.size(), totalCacheSize));
}
private static List<String> getCacheList() {
return CacheManagerRegistry.getCacheNameList();
}
}
三、完整代码
项目结构如下
其中localcache目录就是本地缓存工具的所有代码,而manger是缓存处理器的代码。
配置文件
pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>localcache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>localcache</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spingboot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok框架 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
<!-- mybatis-plus框架 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!-- druid链接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
properties
# 数据库配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis-test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 初始化时建立物理连接的个数
spring.datasource.druid.initial-size=5
# 最大连接池数量
spring.datasource.druid.max-active=30
# 最小连接池数量
spring.datasource.druid.min-idle=5
# 获取连接时最大等待时间,单位毫秒
spring.datasource.druid.max-wait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 连接保持空闲而不被驱逐的最小时间
spring.datasource.druid.min-evictable-idle-time-millis=300000
# 用来检测连接是否有效的sql,要求是一个查询语句
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.test-while-idle=true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-borrow=false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-return=false
# 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
spring.datasource.druid.pool-prepared-statements=false
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=0
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,这行必须注释掉
spring.datasource.druid.filters=stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 合并多个DruidDataSource的监控数据
spring.datasource.druid.use-global-data-source-stat=true
spring.datasource.druid.filter.wall.enabled=true
spring.datasource.druid.filter.wall.db-type=mysql
spring.datasource.druid.filter.stat.db-type=mysql
spring.datasource.druid.filter.stat.enabled=true
# mybatis
mybatis.configuration.auto-mapping-behavior=full
mybatis.configuration.map-underscore-to-camel-case=true
mybatis-plus.mapper-locations=classpath*:/mybatis/mapper/*.xml
代码如下
LocalcacheApplication类
java
package com.example.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(scanBasePackages = "com.example.test")
@EnableScheduling
public class LocalcacheApplication {
public static void main(String[] args) {
SpringApplication.run(LocalcacheApplication.class, args);
}
}
CacheNameEnum类
java
package com.example.test.localcache.constant;
import com.example.test.localcache.CacheNameDomain;
import org.springframework.core.Ordered;
/**
* @description 缓存枚举
*/
public enum CacheNameEnum implements CacheNameDomain {
/**
* 系统配置缓存
*/
SYS_CONFIG("SYS_CONFIG", "系统配置缓存", Ordered.LOWEST_PRECEDENCE),
;
private String name;
private String description;
private int order;
CacheNameEnum(String name, String description, int order) {
this.name = name;
this.description = description;
this.order = order;
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
}
CacheManagerRegistry类
java
package com.example.test.localcache.manager;
import com.example.test.localcache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.OrderComparator;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description 缓存管理器集中注册接口,可以实现对分散在各处的缓存管理器统一维护
*/
@Component
public final class CacheManagerRegistry implements InitializingBean {
/**
* LOGGER
*/
private static final Logger logger = LoggerFactory.getLogger(CacheManagerRegistry.class);
/**
* 缓存管理器
*/
private static Map<String, CacheManager> managerMap = new ConcurrentHashMap<String, CacheManager>();
/**
* 注册缓存管理器
*
* @param cacheManager 缓存管理器
*/
public static void register(CacheManager cacheManager) {
String cacheName = resolveCacheName(cacheManager.getCacheName().getName());
managerMap.put(cacheName, cacheManager);
}
/**
* 刷新特定的缓存
*
* @param cacheName 缓存名称
*/
public static void refreshCache(String cacheName) {
CacheManager cacheManager = managerMap.get(resolveCacheName(cacheName));
if (cacheManager == null) {
logger.warn("cache manager is not exist,cacheName=", cacheName);
return;
}
cacheManager.refreshCache();
cacheManager.dumpCache();
}
/**
* 获取缓存总条数
*/
public static long getCacheSize(String cacheName) {
CacheManager cacheManager = managerMap.get(resolveCacheName(cacheName));
if (cacheManager == null) {
logger.warn("cache manager is not exist,cacheName=", cacheName);
return 0;
}
return cacheManager.getCacheSize();
}
/**
* 获取缓存列表
*
* @return 缓存列表
*/
public static List<String> getCacheNameList() {
List<String> cacheNameList = new ArrayList<>();
managerMap.forEach((k, v) -> {
cacheNameList.add(k);
});
return cacheNameList;
}
public void startup() {
try {
deployCompletion();
} catch (Exception e) {
logger.error("Cache Component Init Fail:", e);
// 系统启动时出现异常,不希望启动应用
throw new RuntimeException("启动加载失败", e);
}
}
/**
* 部署完成,执行缓存初始化
*/
private void deployCompletion() {
List<CacheManager> managers = new ArrayList<CacheManager>(managerMap.values());
// 根据缓存级别进行排序,以此顺序进行缓存的初始化
Collections.sort(managers, new OrderComparator());
// 打印系统启动日志
logger.info("cache manager component extensions:");
for (CacheManager cacheManager : managers) {
String beanName = cacheManager.getClass().getSimpleName();
logger.info(cacheManager.getCacheName().getName(), "==>", beanName);
}
// 初始化缓存
for (CacheManager cacheManager : managers) {
cacheManager.initCache();
cacheManager.dumpCache();
}
}
/**
* 解析缓存名称,大小写不敏感,增强刷新的容错能力
*
* @param cacheName 缓存名称
* @return 转换大写的缓存名称
*/
private static String resolveCacheName(String cacheName) {
return cacheName.toUpperCase();
}
@Override
public void afterPropertiesSet() throws Exception {
startup();
}
}
CacheManagerTrigger类
java
package com.example.test.localcache.manager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.text.MessageFormat;
import java.util.List;
/**
* @description 定时、并按Order顺序刷新缓存
*/
@Component
public class CacheManagerTrigger {
/**
* logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(CacheManagerTrigger.class);
/**
* 触发刷新缓存
*/
@Scheduled(fixedRate = 1000 * 60, initialDelay = 1000 * 60)
private static void refreshCache() {
List<String> cacheNameList = getCacheList();
LOGGER.info("start refresh instruction ,cacheNameList={}", cacheNameList);
if (CollectionUtils.isEmpty(cacheNameList)) {
LOGGER.warn("cache name list are empty");
return;
}
long totalCacheSize = 0;
for (String cacheName : cacheNameList) {
CacheManagerRegistry.refreshCache(cacheName);
totalCacheSize += CacheManagerRegistry.getCacheSize(cacheName);
}
LOGGER.info(MessageFormat.format("缓存刷新成功,缓存管理器:{0}个,总缓存条目数量:{1}条", cacheNameList.size(), totalCacheSize));
}
private static List<String> getCacheList() {
return CacheManagerRegistry.getCacheNameList();
}
}
AbstractLazyCacheSupport类
java
package com.example.test.localcache.support;
import java.util.Observable;
import java.util.Observer;
/**
* @description 支持缓存懒加载
*/
public abstract class AbstractLazyCacheSupport extends Observable {
/**
* 缓存管理观察者
*/
protected Observer cacheManagerObserver;
/**
* 是否已经初始化
*
* @return 是否已经初始化
*/
protected abstract boolean alreadyInitCache();
/**
* 懒加载策略初始化缓存
*/
protected void lazyInitIfNeed() {
if (alreadyInitCache()) {
return;
}
// 单点代码显式锁
synchronized (AbstractLazyCacheSupport.class) {
// 再次检查是否已经初始化
if (alreadyInitCache()) {
return;
}
this.addObserver(cacheManagerObserver);
this.setChanged();
this.notifyObservers();
}
}
/**
* Setter method for property <tt>cacheManagerObserver</tt>.
*
* @param cacheManagerObserver value to be assigned to property cacheManagerObserver
*/
public void setCacheManagerObserver(Observer cacheManagerObserver) {
this.cacheManagerObserver = cacheManagerObserver;
}
}
CacheMessageUtil类
java
package com.example.test.localcache.util;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @description 缓存信息转换工具,以便dump出更友好的缓存信息
*/
public final class CacheMessageUtil {
/** 换行符 */
private static final char ENTERSTR = '\n';
/** Map 等于符号 */
private static final char MAP_EQUAL = '=';
/**
* 禁用构造函数
*/
private CacheMessageUtil() {
// 禁用构造函数
}
/**
* 缓存信息转换工具,以便dump出更友好的缓存信息<br>
* 对于List<?>的类型转换
*
* @param cacheDatas 缓存数据列表
* @return 缓存信息
*/
public static String toString(List<?> cacheDatas) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < cacheDatas.size(); i++) {
Object object = cacheDatas.get(i);
builder.append(object);
if (i != cacheDatas.size() - 1) {
builder.append(ENTERSTR);
}
}
return builder.toString();
}
/**
* 缓存信息转换工具,以便dump出更友好的缓存信息<br>
* 对于Map<String, Object>的类型转换
*
* @param map 缓存数据
* @return 缓存信息
*/
public static String toString(Map<?, ?> map) {
StringBuilder builder = new StringBuilder();
int count = map.size();
for (Iterator<?> i = map.keySet().iterator(); i.hasNext();) {
Object name = i.next();
count++;
builder.append(name).append(MAP_EQUAL);
builder.append(map.get(name));
if (count != count - 1) {
builder.append(ENTERSTR);
}
}
return builder.toString();
}
}
AbstractCacheManager类
java
package com.example.test.localcache;
import com.example.test.localcache.manager.CacheManagerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
/**
* @description 缓存管理抽象类,缓存管理器都要集成这个抽象类
*/
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
/**
* LOGGER
*/
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractCacheManager.class);
/**
* 获取可读性好的缓存信息,用于日志打印操作
*
* @return 缓存信息
*/
protected abstract String getCacheInfo();
/**
* 查询数据仓库,并加载到缓存数据
*/
protected abstract void loadingCache();
/**
* 查询缓存大小
*
* @return
*/
protected abstract long getSize();
/**
* @see InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
CacheManagerRegistry.register(this);
}
@Override
public void initCache() {
String description = getCacheName().getDescription();
LOGGER.info("start init {}", description);
loadingCache();
afterInitCache();
LOGGER.info("{} end init", description);
}
@Override
public void refreshCache() {
String description = getCacheName().getDescription();
LOGGER.info("start refresh {}", description);
loadingCache();
afterRefreshCache();
LOGGER.info("{} end refresh", description);
}
/**
* @see org.springframework.core.Ordered#getOrder()
*/
@Override
public int getOrder() {
return getCacheName().getOrder();
}
@Override
public void dumpCache() {
String description = getCacheName().getDescription();
LOGGER.info("start print {} {}{}", description, "\n", getCacheInfo());
LOGGER.info("{} end print", description);
}
/**
* 获取缓存条目
*
* @return
*/
@Override
public long getCacheSize() {
LOGGER.info("Cache Size Count: {}", getSize());
return getSize();
}
/**
* 刷新之后,其他业务处理,比如监听器的注册
*/
protected void afterInitCache() {
//有需要后续动作的缓存实现
}
/**
* 刷新之后,其他业务处理,比如缓存变通通知
*/
protected void afterRefreshCache() {
//有需要后续动作的缓存实现
}
}
CacheManager类
java
package com.example.test.localcache;
import org.springframework.core.Ordered;
/**
* @description 缓存管理必须实现的接口, 提供刷新机制,为localcache提供缓存操作基础服务
*/
public interface CacheManager extends Ordered {
/**
* 初始化缓存
*/
public void initCache();
/**
* 刷新缓存
*/
public void refreshCache();
/**
* 获取缓存的名称
*
* @return 缓存名称
*/
public CacheNameDomain getCacheName();
/**
* 打印缓存信息
*/
public void dumpCache();
/**
* 获取缓存条数
*
* @return
*/
public long getCacheSize();
}
CacheNameDomain类
java
package com.example.test.localcache;
/**
* @description 缓存名称模型接口定义,每个组件的使用者可以通过枚举的方式实现这个模型接口
*/
public interface CacheNameDomain {
/**
* 缓存初始化顺序,级别越低,越早被初始化
* <p>
* 如果缓存的加载存在一定的依赖关系,通过缓存级别控制初始化或者刷新时缓存数据的加载顺序<br>
* 级别越低,越早被初始化<br>
* <p>
* 如果缓存的加载没有依赖关系,可以使用默认顺序<code>Ordered.LOWEST_PRECEDENCE</code>
*
* @return 初始化顺序
* @see org.springframework.core.Ordered
*/
int getOrder();
/**
* 缓存名称,推荐使用英文大写字母表示
*
* @return 缓存名称
*/
String getName();
/**
* 缓存描述信息,用于打印日志
*
* @return 缓存描述信息
*/
String getDescription();
}
SysConfigCacheManager类
java
package com.example.test.manger;
import com.example.test.localcache.AbstractCacheManager;
import com.example.test.localcache.CacheNameDomain;
import com.example.test.localcache.constant.CacheNameEnum;
import com.example.test.localcache.util.CacheMessageUtil;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 系统配置管理器
*
*/
@Component
public class SysConfigCacheManager extends AbstractCacheManager {
private static final Lock LOCK = new ReentrantLock();
/**
* KEY: 自定义
*/
private static ConcurrentMap<String, Object> CACHE;
@Override
protected String getCacheInfo() {
return CacheMessageUtil.toString(CACHE);
}
@Override
protected void loadingCache() {
LOCK.lock();
try {
CACHE = new ConcurrentHashMap<>();
CACHE.put("key1","value1");
CACHE.put("key2","value2");
CACHE.put("key3","value3");
} finally {
LOCK.unlock();
}
}
@Override
protected long getSize() {
return null == CACHE ? 0 : CACHE.size();
}
@Override
public CacheNameDomain getCacheName() {
return CacheNameEnum.SYS_CONFIG;
}
}
运行效果
我这里是一分钟刷新一次缓存
四、总结分析
SysConfigCacheManager在继承AbstractCacheManager之后,需要实现getCacheInfo、loadingCache、getSize、getCacheName四个方法,其中getCacheName是该缓存处理器的基础信息,为了防止loadingCache出现读写问题,我加了一个可重入锁。
我在例子中没有连接数据库,实际上只要将SysConfigCacheManager的CACHE的数据从数据库读取就可以实现了。我这里只写了一个SysConfigCacheManager,实际业务中可以写很多个处理器,只要继承AbstractCacheManager即可,不过要注意getCacheName的名字不要重复了,不然CacheManagerRegistry只会取最新的一个。
最后提醒大家一下,这个是本地缓存哈,不支持分布式的。