使用 SpringBoot 框架手撸一个本地缓存工具!

发布时间:2024年01月23日

在实现本地缓存的时候,我们经常使用线程安全的ConcurrentHashMap来暂存数据,然后加上SpringBoot自带的@Scheduled定时刷新缓存。虽然这样可以实现本地缓存,但既不优雅也不安全。

那看一下我的思路,首先看一张图!

图片

1.每个处理器都有缓存名字、描述信息、缓存初始化顺序等信息,所以应该定义一个接口,名字为CacheNameDomain

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;

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

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

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来实现

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?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

#?数据库配置
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类

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类

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类

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类

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类

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类

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类

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类

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类

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类

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;
????}

}
运行效果

图片

四、总结分析

我这里没有使用Redis作为缓存组件,直接使用的ConcurrentHashMap,有条件的同学可以替换成Redis,效果会更好。

SysConfigCacheManager在继承AbstractCacheManager之后,需要实现getCacheInfoloadingCachegetSizegetCacheName四个方法,其中getCacheName是该缓存处理器的基础信息,为了防止loadingCache出现读写问题,我加了一个可重入锁。

我在例子中没有连接数据库,实际上只要将SysConfigCacheManager的CACHE的数据从数据库读取就可以实现了。我这里只写了一个SysConfigCacheManager,实际业务中可以写很多个处理器,只要继承AbstractCacheManager即可,不过要注意getCacheName的名字不要重复了,不然CacheManagerRegistry只会取最新的一个。

最后提醒大家一下,这个是本地缓存哈,不支持分布式的。

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记??就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

文章来源:https://blog.csdn.net/weixin_45334346/article/details/135792692
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。