Master Two‑Level Java Cache: From Guava Local Cache to Redis Distributed Cache
This article walks through building a two‑level caching system in Java, starting with a generic cache interface, implementing a local Guava cache, then adding a Redis distributed cache, and finally combining them with versioned keys to achieve timely expiration and seamless cache switching.
Cache is one of the most effective ways to improve system performance, and mastering cache usage is a basic skill for excellent programmers.
1. Generic Cache Interface
1.1 Cache Basic Algorithms
FIFO (First In First Out) – remove the earliest entry when the cache is full.
LFU (Least Frequently Used) – remove entries that are accessed least often.
LRU (Least Recently Used) – remove the least recently accessed entry.
1.2 Interface Definition
Define a generic cache provider interface as follows:
package com.power.demo.cache.contract;
import java.util.function.Function;
/**
* Cache provider interface
*/
public interface CacheProviderService {
<T extends Object> T get(String key);
<T extends Object> T get(String key, Function<String, T> function);
<T extends Object, M extends Object> T get(String key, Function<M, T> function, M funcParm);
<T extends Object> T get(String key, Function<String, T> function, Long expireTime);
<T extends Object, M extends Object> T get(String key, Function<M, T> function, M funcParm, Long expireTime);
<T extends Object> void set(String key, T obj);
<T extends Object> void set(String key, T obj, Long expireTime);
void remove(String key);
boolean contains(String key);
}Note: this list excludes statistics, distributed lock, and increment functions.
2. Local Cache
Local cache (JVM level) can be implemented with Guava Cache.
2.1 What is Guava
Guava is a powerful Java library providing utilities, including caching.
2.2 Add Dependency
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>2.3 Implement Interface
/* Local cache provider (Guava Cache) */
@Configuration
@ComponentScan(basePackages = AppConst.BASE_PACKAGE_NAME)
@Qualifier("localCacheService")
public class LocalCacheProviderImpl implements CacheProviderService {
private static Map<String, Cache<String, Object>> _cacheMap = Maps.newConcurrentMap();
static {
Cache<String, Object> cacheContainer = CacheBuilder.newBuilder()
.maximumSize(AppConst.CACHE_MAXIMUM_SIZE)
.expireAfterWrite(AppConst.CACHE_MINUTE, TimeUnit.MILLISECONDS)
.recordStats()
.build();
_cacheMap.put(String.valueOf(AppConst.CACHE_MINUTE), cacheContainer);
}
@Override
public <T extends Object> T get(String key) {
T obj = get(key, null, null, AppConst.CACHE_MINUTE);
return obj;
}
@Override
public <T extends Object> T get(String key, Function<String, T> function) {
T obj = get(key, function, key, AppConst.CACHE_MINUTE);
return obj;
}
@Override
public <T extends Object, M extends Object> T get(String key, Function<M, T> function, M funcParm) {
T obj = get(key, function, funcParm, AppConst.CACHE_MINUTE);
return obj;
}
@Override
public <T extends Object> T get(String key, Function<String, T> function, Long expireTime) {
T obj = get(key, function, key, expireTime);
return obj;
}
@Override
public <T extends Object, M extends Object> T get(String key, Function<M, T> function, M funcParm, Long expireTime) {
if (StringUtils.isEmpty(key)) {
return null;
}
expireTime = getExpireTime(expireTime);
Cache<String, Object> cacheContainer = getCacheContainer(expireTime);
try {
if (function == null) {
obj = (T) cacheContainer.getIfPresent(key);
} else {
final Long cachedTime = expireTime;
obj = (T) cacheContainer.get(key, () -> function.apply(funcParm));
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
@Override
public <T extends Object> void set(String key, T obj) {
set(key, obj, AppConst.CACHE_MINUTE);
}
@Override
public <T extends Object> void set(String key, T obj, Long expireTime) {
if (StringUtils.isEmpty(key) || obj == null) {
return;
}
expireTime = getExpireTime(expireTime);
Cache<String, Object> cacheContainer = getCacheContainer(expireTime);
cacheContainer.put(key, obj);
}
@Override
public void remove(String key) {
if (StringUtils.isEmpty(key)) {
return;
}
long expireTime = getExpireTime(AppConst.CACHE_MINUTE);
Cache<String, Object> cacheContainer = getCacheContainer(expireTime);
cacheContainer.invalidate(key);
}
@Override
public boolean contains(String key) {
if (StringUtils.isEmpty(key)) {
return false;
}
Object obj = get(key);
return obj != null;
}
private static final Lock lock = new ReentrantLock();
private Cache<String, Object> getCacheContainer(Long expireTime) {
if (expireTime == null) {
return null;
}
String mapKey = String.valueOf(expireTime);
if (_cacheMap.containsKey(mapKey)) {
return _cacheMap.get(mapKey);
}
lock.lock();
try {
Cache<String, Object> cacheContainer = CacheBuilder.newBuilder()
.maximumSize(AppConst.CACHE_MAXIMUM_SIZE)
.expireAfterWrite(expireTime, TimeUnit.MILLISECONDS)
.recordStats()
.build();
_cacheMap.put(mapKey, cacheContainer);
return cacheContainer;
} finally {
lock.unlock();
}
}
private Long getExpireTime(Long expireTime) {
if (expireTime == null || expireTime < AppConst.CACHE_MINUTE / 10) {
return AppConst.CACHE_MINUTE;
}
return expireTime;
}
}Guava Cache supports FIFO, LRU, and LFU policies. Different expiration times require separate CacheBuilder instances; the implementation handles this automatically.
3. Distributed Cache (Redis)
Redis is a high‑performance key‑value store widely used for caching.
3.1 Add Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>3.2 Configure Redis
# Redis cache configuration
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123321
spring.redis.timeout=300000ms
spring.redis.jedis.pool.max-active=512
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms3.3 Redis Cache Provider Implementation
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(factory);
return template;
}
}RedisTemplate supports various serializers; using StringRedisSerializer for keys improves operability.
@Configuration
@ComponentScan(basePackages = AppConst.BASE_PACKAGE_NAME)
@Qualifier("redisCacheService")
public class RedisCacheProviderImpl implements CacheProviderService {
@Resource
private RedisTemplate<Serializable, Object> redisTemplate;
@Override
public <T extends Object> T get(String key) {
T obj = get(key, null, null, AppConst.CACHE_MINUTE);
return obj;
}
@Override
public <T extends Object> T get(String key, Function<String, T> function) {
T obj = get(key, function, key, AppConst.CACHE_MINUTE);
return obj;
}
@Override
public <T extends Object, M extends Object> T get(String key, Function<M, T> function, M funcParm) {
T obj = get(key, function, funcParm, AppConst.CACHE_MINUTE);
return obj;
}
@Override
public <T extends Object> T get(String key, Function<String, T> function, Long expireTime) {
T obj = get(key, function, key, expireTime);
return obj;
}
@Override
public <T extends Object, M extends Object> T get(String key, Function<M, T> function, M funcParm, Long expireTime) {
if (StringUtils.isEmpty(key)) {
return null;
}
expireTime = getExpireTime(expireTime);
try {
ValueOperations<Serializable, Object> ops = redisTemplate.opsForValue();
T obj = (T) ops.get(key);
if (function != null && obj == null) {
obj = function.apply(funcParm);
if (obj != null) {
set(key, obj, expireTime);
}
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public <T extends Object> void set(String key, T obj) {
set(key, obj, AppConst.CACHE_MINUTE);
}
@Override
public <T extends Object> void set(String key, T obj, Long expireTime) {
if (StringUtils.isEmpty(key) || obj == null) {
return;
}
expireTime = getExpireTime(expireTime);
ValueOperations<Serializable, Object> ops = redisTemplate.opsForValue();
ops.set(key, obj);
redisTemplate.expire(key, expireTime, TimeUnit.MILLISECONDS);
}
@Override
public void remove(String key) {
if (StringUtils.isEmpty(key)) {
return;
}
redisTemplate.delete(key);
}
@Override
public boolean contains(String key) {
if (StringUtils.isEmpty(key)) {
return false;
}
Object obj = get(key);
return obj != null;
}
private Long getExpireTime(Long expireTime) {
if (expireTime == null || expireTime < AppConst.CACHE_MINUTE / 10) {
return AppConst.CACHE_MINUTE;
}
return expireTime;
}
}Note: RedisTemplate’s default serializers are JdkSerializationRedisSerializer ; using StringRedisSerializer for keys and Jackson2JsonRedisSerializer for values is recommended, though deserialization issues may arise.
4. Cache “Timely” Expiration Issue
Real‑time expiration can be achieved by adding a version suffix to cache keys; resetting the version forces all related entries to become stale.
5. Two‑Level Cache Builder
A façade class PowerCacheBuilder combines local and Redis providers, supports versioned keys, and offers get/set/remove methods that automatically update both layers.
public class PowerCacheBuilder {
@Autowired @Qualifier("localCacheService")
private CacheProviderService localCacheService;
@Autowired @Qualifier("redisCacheService")
private CacheProviderService redisCacheService;
// Initialization, get, set, remove, version handling methods ...
}Unit tests demonstrate cache version reset and key generation.
References: http://ifeve.com/google-guava/ http://www.cnblogs.com/luochengqiuse/p/4640932.html
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
