SoFunction
Updated on 2025-04-16

SpringCache Cache Abstract CacheManager and Custom Key Generation Method

In the development of high-performance Java application, caching is a key technology to improve system response speed and reduce database burden. Spring Framework provides an elegant caching abstraction layer that enables developers to integrate various cache implementations in a declarative manner.

1. Spring Cache infrastructure

1.1 Cache abstract design concept

Spring Cache's design follows Spring's consistent philosophy: providing high-level abstraction for specific technologies and reducing the coupling between implementation and business code. The cache abstraction layer is driven by annotation and supports declarative configuration, which greatly simplifies the amount of code for cache operations. Developers only need to focus on cache policies and do not need to write duplicate cache logic.

This design makes switching between different cache providers extremely simple, enhancing the maintainability and scalability of the application.

// Example of key annotations for cache abstractions@Service
public class ProductService {
    
    @Cacheable(value = "products", key = "#id")
    public Product getProductById(Long id) {
        // The method call will be cached, and repeated calls with the same parameters will directly return the cached result        return (id).orElse(null);
    }
    
    @CacheEvict(value = "products", key = "#")
    public void updateProduct(Product product) {
        // Update product information and clear corresponding cache entries        (product);
    }
    
    @CachePut(value = "products", key = "#")
    public Product createProduct(Product product) {
        // Create a product and update the cache, and return the results        return (product);
    }
    
    @CacheEvict(value = "products", allEntries = true)
    public void clearProductCache() {
        // Clear all entries in the products cache        ("Product cache has been cleared");
    }
}

1.2 Overview of core components

The Spring Cache architecture consists of several core components, each performs its own duties and works in concert. The Cache interface defines the basic behavior of cache operations; the CacheManager is responsible for creating, configuring and managing Cache instances; the KeyGenerator is responsible for generating unique keys for cache entries; and the CacheResolver decides which cache to use at runtime. Together, these components form a flexible and powerful caching framework. Among them, CacheManager is the bridge connecting cache abstraction and concrete implementation, and is the core of the entire architecture.

// Spring Cache core interface relationshippublic interface Cache {
    // The name of the cache is used to identify different caches    String getName();
    
    // The underlying native cache can be converted to a specific implementation    Object getNativeCache();
    
    // Get cached value according to key    ValueWrapper get(Object key);
    
    // Save the value into cache    void put(Object key, Object value);
    
    // Remove the entry for the specified key from the cache    void evict(Object key);
    
    // Clear all entries in the cache    void clear();
}

// CacheManager definitionpublic interface CacheManager {
    // Get the cache of the specified name    Cache getCache(String name);
    
    // Get a collection of all cache names    Collection<String> getCacheNames();
}

2. In-depth analysis of CacheManager

2.1 Commonly used CacheManager implementation

The Spring framework provides a variety of CacheManager implementations, supporting different caching technologies. ConcurrentMapCacheManager is a simple implementation based on ConcurrentHashMap, suitable for development and testing environments; EhCacheCacheManager integrates the advanced features of EhCache; RedisCacheManager provides integration with Redis distributed cache, suitable for production environments. It is crucial to choose the right CacheManager based on application requirements and performance requirements. Each implementation has its own unique configuration and performance characteristics.

// Configuration examples of different CacheManagers@Configuration
@EnableCaching
public class CacheConfig {
    
    // Simple memory cache configuration    @Bean
    public CacheManager concurrentMapCacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        (("products", "customers"));
        return cacheManager;
    }
    
    // Redis cache configuration    @Bean
    public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = ()
                .entryTtl((10))  // Set cache expiration time                .serializeKeysWith(
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));
                
        return (connectionFactory)
                .cacheDefaults(config)
                .withCacheConfiguration("products", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl((5)))
                .build();
    }
    
    // Caffeine cache configuration    @Bean
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        
        // Set different configurations for different caches        ("products=maximumSize=500,expireAfterWrite=5m");
        ("customers=maximumSize=1000,expireAfterWrite=10m");
        
        return cacheManager;
    }
}

2.2 Composite caching strategy

In complex applications, a single caching strategy often fails to meet all needs. Spring provides CompositeCacheManager, which allows combining multiple CacheManagers to build a multi-level caching system. For example, local cache (Caffeine) and distributed cache (Redis) can be combined, the former providing high-speed access and the latter ensuring cluster consistency. Compound strategies need to reasonably plan cached data flow and consistency maintenance mechanism to avoid data inconsistency.

// Composite cache manager configuration@Bean
public CacheManager compositeCacheManager(
        CaffeineCacheManager caffeineCacheManager,
        RedisCacheManager redisCacheManager) {
    
    // Create a composite cache manager    CompositeCacheManager compositeCacheManager = new CompositeCacheManager(
            caffeineCacheManager,
            redisCacheManager
    );
    
    // Set the fallback mechanism to create a default cache when the specified cache does not exist    (true);
    
    return compositeCacheManager;
}

// Example of cache usage policy@Service
public class TieredCacheService {
    
    // Use local cache, suitable for high-frequency data access    @Cacheable(value = "localProducts", cacheManager = "caffeineCacheManager")
    public Product getProductForFrontend(Long id) {
        return (id).orElse(null);
    }
    
    // Use distributed cache, suitable for cluster sharing of data    @Cacheable(value = "sharedProducts", cacheManager = "redisCacheManager")
    public Product getProductForApi(Long id) {
        return (id).orElse(null);
    }
    
    // Synchronous update of two-level cache    @Caching(evict = {
        @CacheEvict(value = "localProducts", key = "#", cacheManager = "caffeineCacheManager"),
        @CacheEvict(value = "sharedProducts", key = "#", cacheManager = "redisCacheManager")
    })
    public void updateProduct(Product product) {
        (product);
    }
}

3. Custom key generation strategy

3.1 Default key generation mechanism

Spring Cache uses SimpleKeyGenerator to generate cache keys by default. For the non-parameter method, use as the key; for the single-parameter method, use this parameter as the key; for the multi-parameter method, use a SimpleKey instance containing all parameters. This mechanism is simple and practical, but may cause key conflicts or be difficult to manage in complex scenarios. The default key generation logic lacks object attribute selection ability and cannot handle complex objects containing non-cache-related fields.

// Default key generator implementation logic diagrampublic class SimpleKeyGenerator implements KeyGenerator {
    
    @Override
    public Object generate(Object target, Method method, Object... params) {
        if ( == 0) {
            return ;
        }
        if ( == 1) {
            Object param = params[0];
            if (param != null && !().isArray()) {
                return param;
            }
        }
        return new SimpleKey(params);
    }
}

// Example of default key generation@Cacheable("products") // Use the default key generatorpublic Product getProduct(Long id, String region) {
    // The cache key will be SimpleKey(id, region)    return (id, region);
}

3.2 Custom KeyGenerator implementation

Custom KeyGenerator can accurately control the generation logic of cache keys. You can select a specific field combination, apply a hashing algorithm, or add a prefix according to business needs. For example, for complex query parameters, the core field build keys can be extracted; for partitioned data, the tenant ID prefix can be added to avoid conflicts. The custom generator is registered with @Bean and referenced by the keyGenerator property in the @Cacheable annotation.

// Custom key generator implementation@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
    
    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder keyBuilder = new StringBuilder();
        
        // Add class name and method name prefix        (().getSimpleName())
                 .append(".")
                 .append(());
        
        // Process parameters        for (Object param : params) {
            (":");
            if (param instanceof Product) {
                // For product objects, only use ID and name                Product product = (Product) param;
                ("Product[")
                         .append(())
                         .append(",")
                         .append(())
                         .append("]");
            } else {
                // Use toString directly in other types                (param);
            }
        }
        
        return ();
    }
}

// Register in the configuration class@Bean
public KeyGenerator customKeyGenerator() {
    return new CustomKeyGenerator();
}

// Use custom key generator@Cacheable(value = "products", keyGenerator = "customKeyGenerator")
public List<Product> findProductsByCategory(String category, boolean includeInactive) {
    // The key will look like: ":Electronics:false"    return (category, includeInactive);
}

3.3 SpEL expression custom cache key

Spring Expression Language (SpEL) provides flexible ways to customize cache keys without creating additional classes. Specifying expressions through the key attribute allows you to build keys from method parameters, return values, or context. SpEL supports string operations, conditional logic and object navigation, and can handle complex key generation requirements. In a multi-tenant system, an isolated cache key can be constructed by combining SecurityContext to obtain tenant information.

// SpEL expression cache key example@Service
public class AdvancedCacheService {
    
    // Use method parameter combination to build key    @Cacheable(value = "productSearch", key = "#category + '_' + #minPrice + '_' + #maxPrice")
    public List<Product> searchProducts(String category, Double minPrice, Double maxPrice) {
        return (category, minPrice, maxPrice);
    }
    
    // Use object properties    @Cacheable(value = "userProfile", key = "# + '_' + #")
    public UserProfile getUserProfile(User user) {
        return (user);
    }
    
    // Use conditional expressions    @Cacheable(value = "reports", 
               key = "#reportType + (T().valueOf(#detailed ? '_detailed' : '_summary'))",
               condition = "#reportType != 'REALTIME'") // Real-time report does not cache    public Report generateReport(String reportType, boolean detailed) {
        return (reportType, detailed);
    }
    
    // Combine built-in objects and methods    @Cacheable(value = "securedData", 
               key = "#() + '_' + #dataId",
               unless = "#result == null")
    public SecuredData getSecuredData(String dataId) {
        return (dataId);
    }
    
    // Helper method, used in SpEL expressions    public String getTenantPrefix() {
        return ().getAuthentication().getName() + "_tenant";
    }
}

4. Cache design in practice

4.1 Cache Coherence Policy

Cache consistency is a key challenge in system design. In Spring Cache, consistency is maintained mainly through @CacheEvict and @CachePut. The time-driven policy controls the expiration of the cache by setting the TTL; the event-driven policy actively updates the cache when the data changes. In complex systems, cross-service cache synchronization can be achieved by combining message queues. Regularly refreshing key caches is also an effective means to ensure data freshness. Different scenarios need to weigh consistency and performance.

// Cache consistency maintenance example@Service
public class ConsistentCacheService {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    // Read cached data    @Cacheable(value = "productDetails", key = "#id")
    public ProductDetails getProductDetails(Long id) {
        return (id).orElse(null);
    }
    
    // Update and refresh the cache    @Transactional
    public ProductDetails updateProductDetails(ProductDetails details) {
        // Save the data first        ProductDetails saved = (details);
        
        // Publish cache update event        (new ProductCacheInvalidationEvent(()));
        
        return saved;
    }
    
    // Event listener, handle cache refresh    @EventListener
    public void handleProductCacheInvalidation(ProductCacheInvalidationEvent event) {
        clearProductCache(());
    }
    
    // Clear specific product cache    @CacheEvict(value = "productDetails", key = "#id")
    public void clearProductCache(Long id) {
        // The method body can be empty, the annotation processing cache is cleared        ("Product cache cleared: " + id);
    }
    
    // Cache event definition    public static class ProductCacheInvalidationEvent {
        private final Long productId;
        
        public ProductCacheInvalidationEvent(Long productId) {
             = productId;
        }
        
        public Long getProductId() {
            return productId;
        }
    }
}

Summarize

Spring Cache abstraction layer provides powerful and flexible caching support for Java applications through unified interfaces and declarative annotations. As a core component, CacheManager connects cache abstraction and concrete implementation, supporting various scenarios from simple memory cache to complex distributed cache.

Custom key generation strategies, whether implemented through KeyGenerator or customization of SpEL expressions, provide powerful tools for precise control of cache behavior.

In practical applications, rational selection of CacheManager, designing cache key policies, and maintaining cache consistency are the keys to building a high-performance cache system.

The above is personal experience. I hope you can give you a reference and I hope you can support me more.