搞起来,使用 SpringBoot 框架徒手撸一个安全、可靠的本地缓存工具

戳上方蓝字“Java面试题精选”关注!

一、前言

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

一个好的本地缓存工具应该是这样搭建的

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

这样缓存组件有什么用呢?

  • 缓存数据库查询结果:当应用程序需要频繁查询数据库中相同的数据时,可以将查询结果缓存在本地,以减少数据库的压力和提高响应速度。
  • 缓存配置信息:应用程序的配置信息通常不会频繁变更,可以将配置信息缓存在本地,避免每次都从配置文件或配置中心读取。
  • 缓存计算结果:对于耗时的计算任务,可以将计算结果缓存在本地,下次需要时直接从缓存获取。
  • 缓存静态资源:将静态资源如图片、CSS和JS文件缓存在本地,减少网络传输,提高页面加载速度。
  • 缓存热点数据:对于高频访问的数据,可以将其缓存在本地,减少数据库的IO压力。

二、我的思路

主要包括下面几大项

搞起来,使用 SpringBoot 框架徒手撸一个安全、可靠的本地缓存工具

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 CacheManagerInitializingBean {

    /**
     * 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是缓存处理器的代码。

搞起来,使用 SpringBoot 框架徒手撸一个安全、可靠的本地缓存工具

配置文件

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.classargs);
    }

}

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 CacheManagerInitializingBean {

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

}

运行效果

我这里是一分钟刷新一次缓存

搞起来,使用 SpringBoot 框架徒手撸一个安全、可靠的本地缓存工具

四、总结分析

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

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

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

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

来源:juejin.cn/post/7303465518157021225


后端专属技术群

构建高质量的技术交流社群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!

文明发言,以交流技术职位内推行业探讨为主

广告人士勿入,切勿轻信私聊,防止被骗

搞起来,使用 SpringBoot 框架徒手撸一个安全、可靠的本地缓存工具

加我好友,拉你进群

原文始发于微信公众号(Java面试题精选):搞起来,使用 SpringBoot 框架徒手撸一个安全、可靠的本地缓存工具

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/202878.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!