使用Springboot框架手撸一个安全、可靠的本地缓存工具

news2025/1/15 14:23:07

前言🍓

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

1. DAL实现,产出DAO和DO对象,定义缓存领域模型
2. 定义缓存名称,特别关注缓存的初始化顺序
3. 编写数据仓库,通过模型转换器实现数据模型到缓存模型的转化
4. 编写缓存管理器,推荐继承抽象管理器 {@link AbstractCacheManager}
5. 根据业务需求,设计缓存数据接口(putAll,get,getCacheInfo等基础API)
6. 完成bean配置,最好是可插拔的注册方式,缓存管理器和数据仓库、扩展点服务

我的思路⭐️

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

}

运行效果

我这里是一分钟刷新一次缓存
在这里插入图片描述

总结分析

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/94103.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python 之 numpy图片处理 矩阵操作

目录 一&#xff1a;垂直方向翻转(行逆序) 二&#xff1a;水平方向翻转(列逆序) 三&#xff1a;垂直、水平方向翻转(行、列逆序) 四&#xff1a;调整亮度&#xff0c;变明亮*2.0 五&#xff1a;调整亮度&#xff0c;变暗 六&#xff1a;垂直方向裁剪 七&#xff1a;水平…

ESP32 ESP-IDF LVGL8 显示中文

陈拓 2022/12/15-2022/12/16 1. 开发环境 《ESP32 ESP-IDF LVGL8.3.3移植》 ESP32 ESP-IDF LVGL8.3.3移植_晨之清风的博客-CSDN博客ESP32 ESP-IDF LVGL8.3.3移植。https://blog.csdn.net/chentuo2000/article/details/128269394?spm1001.2014.3001.5501 2. 使用LVGL自带的中…

C# 创建WebService接口并连接

创建WebService项目 首先安装下.NET Framework4.6.2-4.7.1开发工具。 然后就是新建 ASP.NET Web应用程序 项目。 输入项目名称WebServiceDemo 选择空&#xff0c;然后先去掉HTTPS配置。 项目创建好之后&#xff0c;开始添加asmx文件. 添加好之后在添加一个有参数的名为Hel…

牛客java刷题知识点总结(六)

内存引用地址 内存引用地址&#xff0c;是指栈中存放的地址&#xff0c;来指向堆中的某个位置。 int 是基本类型&#xff0c;数据直接存放在栈中&#xff0c;不存在内存引用地址的说法 A对 指向常量池里的"hello"。 B对 题中没说声明的a是局部变量。 C错 int a 1;并…

DSPE-PEG-N3,磷脂-聚乙二醇-叠氮 点击化学PEG试剂,可用于药物传递、基因转染和生物分子修饰

中文名称 叠氮聚乙二醇磷脂、磷脂聚乙二醇叠氮 简称 N3-PEG-DSPE、DSPE-PEG-N3 物理性质&#xff1a;米白色/白色固体或粘性液体取决于分子量。 溶剂&#xff1a; 溶于大部分有机溶剂&#xff0c;和水有很好的溶解性。 活性基团&#xff1a; N3 反应基…

深度学习(17)—— 度量学习

深度学习&#xff08;17&#xff09;—— 度量学习 文章目录深度学习&#xff08;17&#xff09;—— 度量学习一、What?二、paired-based loss1. Contrastive loss2. Triplet loss3. Triplet center loss4.N-pair loss5. Quadruplet loss6. Lifted Structure Loss昨天讨论的时…

Linux安装tomcat 保姆级教程

一、安装前提 安装tomcat&#xff0c;前提需要安装jdk&#xff0c;如果没有安装 linux安装jdk教程(手动安装/yum安装) 保姆级教程_ 来杯咖啡的博客-CSDN博客 二、安装教程 1、下载tomcat tar包 官网下载地址: Apache Tomcat - Welcome! 2、将下载的压缩包上…

实验4 路由配置

实验4 路由配置一、实验目的二、实验要求三、实验步骤&#xff0c;数据记录及处理四&#xff0e;实验总结一、实验目的 1、路由配置&#xff1b; 2、配置静态路由&#xff1b; 3、掌握RIP协议配置。 二、实验要求 写出PC0与PC1连通的基本要求和配置过程 三、实验步骤&#…

UDP服务器

目录 一&#xff0c;介绍 二&#xff0c;日志 三&#xff0c;服务端 1&#xff0c;服务器参数 2&#xff0c;创建套接字 3,绑定 3.1 填充套接字信息 3.2 绑定 4&#xff0c;启动服务器 4.1接收客户端请求 4.2记录用户信息 4.3 消息转发 4.4 main函数代码 4.5服务器整…

单商户商城系统功能拆解47—应用中心—自定义海报

单商户商城系统&#xff0c;也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法&#xff0c;例如拼团&#xff0c;秒杀&#xff0c;砍价&#xff0c;包邮…

JVM之class加载过程

一、定义&#xff1a; java虚拟机把描述类的数据从class文件加载到内存&#xff0c;并对数据进行 校验/准备/解析和初始化&#xff0c;最终形成可以被虚拟机直接使用的Java类型&#xff0c;这个过程被称作虚拟机的类加载机制。 称作虚拟机的类加载机制。 loading -> linki…

南卡和FIIL蓝牙耳机哪个比较好?数码达人深度对比评测

2023即将到来&#xff0c;蓝牙耳机对于大部分人来说&#xff0c;成为了越来越重要的存在。日常生活中随处可见的都是在使用蓝牙耳机的人&#xff0c;有听歌的、看剧的、玩游戏的等等。伴随着更多的人使用蓝牙耳机&#xff0c;市面上可见的蓝牙耳机数量正在飙升&#xff0c;这也…

MySQL 的自增主键一定是连续的吗?

全文摘要&#xff1a;结合实例分析了自增值保存在哪里&#xff0c;自增值的修改策略&#xff0c;以及自增值不连续的四个场景&#xff0c;希望对各位小伙伴们有所帮助~ 众所周知&#xff0c;自增主键可以让聚集索引尽量地保持递增顺序插入&#xff0c;避免了随机查询&#xff…

微软确认配置错误导致65,000多家公司的数据泄露

©网络研究院 微软证实&#xff0c;在安全漏洞导致端点无需任何身份验证即可通过互联网公开访问后&#xff0c;它无意中暴露了与数千名客户相关的信息。 微软在警报中表示&#xff1a; “这种错误配置可能导致未经身份验证访问与微软和潜在客户之间的交互相对应的一些业务…

互联网大厂Java岗考点(阿里+百度+腾讯+字节跳动+美团+京东)

本文扼要 本文结构主要分为以下三个部分&#xff1a; 01 互联网大厂考点&#xff08;阿里百度腾讯字节跳动美团京东&#xff09;02 Java 面试考点大全&#xff08;基本功底常用技术技术深度技术经验学习能力工作能力项目经验&#xff09;03 面试真题重现 01 互联网大厂考点 …

OPSS-PEG-N3叠氮聚乙二醇巯基吡啶, N3-PEG-OPSS,点击化学PEG试剂

名称 叠氮聚乙二醇巯基吡啶 N3-PEG-OPSS 中文名称 叠氮PEG巯基吡啶 巯基吡啶PEG叠氮 巯基吡啶聚乙二醇叠氮 英文名称 N3-PEG-OPSS OPSS-PEG-N3 Azide-PEG-OPSS OPSS-PEG-Azide 溶剂 溶于二氯甲烷&#xff0c;氯仿&#xff0c;乙酸乙酯&#xff0c;四氢呋喃等有机溶剂 存储条…

pycharm安装使用pyQt5

一、创建项目 二、安装pyqt5库 三、配置PyCharm外部工具 打开File->settings->Tools->External Tools 1、配置QtDesigner 程序&#xff1a;D:\python3.7\Lib\site-packages\qt5_applications\Qt\bin\designer.exe//安装包的时候回自动下载&#xff0c;直接在包文件…

【微服务技术09】统一网关Gateway

【微服务技术09】统一网关Gateway 案例代码&#xff1a;https://gitee.com/pikachu2333/spring-cloud-hexuan 网关作用 网关功能&#xff1a; 身份认证和权限校验服务路由、负载均衡请求限流 权限控制&#xff1a; 网关作为微服务入口&#xff0c;需要校验用户是是否有请求…

配置Typora

配置Typora 文章目录配置Typora阿里云1&#xff09;网页搜索阿里云OSS2&#xff09;注册账号3&#xff09;点击立刻开通a) 点击“产品价格”b) 初次付费c) 交钱以免造成后续无法访问d&#xff09;进入管理控制台e) 创建钥匙PicGo1&#xff09;下载安装2&#xff09;设置选择显示…

领域知识网络即服务:知识助力产业升级“无形的手”?

文|智能相对论 作者|叶远风 你用过ChatGPT了吗&#xff1f; 这个风靡全球的AI应用&#xff0c;以出圈的方式展现着AI的能量。 一个对话的界面&#xff0c;可以聊任何你想聊的话题&#xff0c;可以写出任何你想要的文字。 似乎有无限的人类知识&#xff0c;被集中到了一起&…