Backend Development 23 min read

Design and Implementation of a Local Cache Component in Spring Boot

This article explains how to build a robust, thread‑safe local cache for Spring Boot applications by defining cache domain models, abstract managers, a centralized registry, and scheduled refresh tasks, while providing complete Java code examples and best‑practice recommendations.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Design and Implementation of a Local Cache Component in Spring Boot

Introduction

When implementing a local cache, developers often resort to using a thread‑safe ConcurrentHashMap together with SpringBoot's @Scheduled annotation. Although functional, this approach is neither elegant nor safe for production use.

Proposed Architecture

A well‑structured local cache should consist of the following components:

DAL layer that produces DAO and DO objects and defines cache domain models.

Cache name definition with initialization order.

Data repository that converts data models to cache models.

Cache manager that extends an abstract manager ( AbstractCacheManager ).

Cache API exposing basic operations such as putAll , get , and getCacheInfo .

Bean configuration that allows plug‑in registration of managers and repositories.

Core Interfaces and Classes

CacheNameDomain defines the cache name, description and order:

package com.example.test.localcache;

public interface CacheNameDomain {
    int getOrder();
    String getName();
    String getDescription();
}

CacheManager is the contract for all cache managers:

package com.example.test.localcache;

import org.springframework.core.Ordered;

public interface CacheManager extends Ordered {
    void initCache();
    void refreshCache();
    CacheNameDomain getCacheName();
    void dumpCache();
    long getCacheSize();
}

The abstract base class provides common lifecycle handling and logging:

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;

public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
    protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractCacheManager.class);
    protected abstract String getCacheInfo();
    protected abstract void loadingCache();
    protected abstract long getSize();

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

    @Override
    public int getOrder() {
        return getCacheName().getOrder();
    }

    @Override
    public void dumpCache() {
        String description = getCacheName().getDescription();
        LOGGER.info("start print {}\n{}", description, getCacheInfo());
        LOGGER.info("{} end print", description);
    }

    @Override
    public long getCacheSize() {
        LOGGER.info("Cache Size Count: {}", getSize());
        return getSize();
    }

    protected void afterInitCache() { /* optional post‑init actions */ }
    protected void afterRefreshCache() { /* optional post‑refresh actions */ }
}

A central registry collects all cache managers and provides unified operations:

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.*;
import java.util.concurrent.ConcurrentHashMap;

@Component
public final class CacheManagerRegistry implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(CacheManagerRegistry.class);
    private static Map
managerMap = new ConcurrentHashMap<>();

    public static void register(CacheManager cacheManager) {
        String cacheName = resolveCacheName(cacheManager.getCacheName().getName());
        managerMap.put(cacheName, cacheManager);
    }

    public static void refreshCache(String cacheName) {
        CacheManager cm = managerMap.get(resolveCacheName(cacheName));
        if (cm == null) { logger.warn("cache manager not exist, cacheName={}", cacheName); return; }
        cm.refreshCache();
        cm.dumpCache();
    }

    public static long getCacheSize(String cacheName) {
        CacheManager cm = managerMap.get(resolveCacheName(cacheName));
        if (cm == null) { logger.warn("cache manager not exist, cacheName={}", cacheName); return 0; }
        return cm.getCacheSize();
    }

    public static List
getCacheNameList() {
        List
list = new ArrayList<>();
        managerMap.forEach((k, v) -> list.add(k));
        return list;
    }

    @Override
    public void afterPropertiesSet() throws Exception { startup(); }

    private void startup() {
        try { deployCompletion(); }
        catch (Exception e) { logger.error("Cache Component Init Fail:", e); throw new RuntimeException("启动加载失败", e); }
    }

    private void deployCompletion() {
        List
managers = new ArrayList<>(managerMap.values());
        Collections.sort(managers, new OrderComparator());
        logger.info("cache manager component extensions:");
        for (CacheManager cm : managers) {
            logger.info(cm.getCacheName().getName() + " ==> " + cm.getClass().getSimpleName());
        }
        for (CacheManager cm : managers) {
            cm.initCache();
            cm.dumpCache();
        }
    }

    private static String resolveCacheName(String cacheName) { return cacheName.toUpperCase(); }
}

A scheduled trigger periodically refreshes all registered caches in order:

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;

@Component
public class CacheManagerTrigger {
    private static final Logger LOGGER = LoggerFactory.getLogger(CacheManagerTrigger.class);

    @Scheduled(fixedRate = 1000 * 60, initialDelay = 1000 * 60)
    private static void refreshCache() {
        List
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 name : cacheNameList) {
            CacheManagerRegistry.refreshCache(name);
            totalCacheSize += CacheManagerRegistry.getCacheSize(name);
        }
        LOGGER.info(MessageFormat.format("缓存刷新成功,缓存管理器:{0}个,总缓存条目数量:{1}条", cacheNameList.size(), totalCacheSize));
    }

    private static List
getCacheList() { return CacheManagerRegistry.getCacheNameList(); }
}

Example Implementation – SysConfigCacheManager

The following concrete manager caches system configuration data using a ConcurrentHashMap and a re‑entrant lock to avoid read/write conflicts:

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.*;

@Component
public class SysConfigCacheManager extends AbstractCacheManager {
    private static final Lock LOCK = new ReentrantLock();
    private static ConcurrentMap
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 CACHE == null ? 0 : CACHE.size(); }

    @Override
    public CacheNameDomain getCacheName() { return CacheNameEnum.SYS_CONFIG; }
}

This manager is automatically registered via AbstractCacheManager.afterPropertiesSet() , and its cache will be refreshed every minute by CacheManagerTrigger .

Conclusion

The article demonstrates a clean, extensible way to build local caches in Spring Boot without external services. Developers can replace the ConcurrentHashMap with Redis or another distributed store for higher scalability, while reusing the same manager, registry, and trigger infrastructure.

JavacacheSpringBootConcurrentHashMapLocalCacheDesignPatternScheduledTask
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.