The question to be discussed today is the same as the question

The principle of

By first source authentication, see source RedisCacheConfiguration# determineConfiguration here to do a thing to this. CacheProperties. GetRedis (); Plug the configuration of of to the RedisCacheConfiguration. CacheNames are arrays in cacheProperties, which means you can set multiple cache fields, but the annotation does not set an expiration time for each cache field, which results in a common expiration time for all cache fields. I don’t know why the person who wrote this code did this, but maybe he thought the common business scenario was that a key might have different caches, or maybe he thought that an application should only let you use one cache, so the expiration time should be set to the same, right? However, in the actual scenario, we often use the cache of multiple business scenarios in one application. For example: user center, you may need to cache menus, you may need to cache user login information, obviously the two caches cannot use the same strategy.

class RedisCacheConfiguration {
/ /... Omit irrelevant code
   private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration( ClassLoader classLoader) {
      if (this.redisCacheConfiguration ! =null) {
         return this.redisCacheConfiguration;
      }
      Redis redisProperties = this.cacheProperties.getRedis();
      org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
            .defaultCacheConfig();
      config = config.serializeValuesWith(SerializationPair
            .fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
      if(redisProperties.getTimeToLive() ! =null) {
         config = config.entryTtl(redisProperties.getTimeToLive());
      }
      if(redisProperties.getKeyPrefix() ! =null) {
         config = config.prefixKeysWith(redisProperties.getKeyPrefix());
      }
      if(! redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); }if(! redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); }returnconfig; }}Copy the code

From the above code we can infer that all cache fields map to the same Redis configuration instance. So if we need to change the expiration time to different cache fields, we just need to construct the following data structure.

@ConfigurationProperties(prefix = "spring.cache.custom.redis")
public class CustomRedisProperties {
    //spring.cache.custom.redis.user.time-to-live=5000ms
    //spring.cache.custom.redis.order.time-to-live=10000ms
    / / map structure
    / / 1, (" user ", new Redis (.) setTimeToLive (5000 ms)
    / / 2, (" order ", the new Redis (.) setTimeToLive (10000 ms)
    private Map<String, Redis> cacheNamesMap;

    public Map<String, Redis> getCacheNamesMap(a) {
        return cacheNamesMap;
    }

    public void setCacheNamesMap(Map<String, Redis> cacheNamesMap) {
        this.cacheNamesMap = cacheNamesMap; }}Copy the code

Next we implement a configuration class to configure CacheManager

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableConfigurationProperties({ CacheProperties.class,CustomRedisProperties.class})
public class CacheMapRedisCacheConfiguration {

    private CacheProperties cacheProperties;
    private CustomRedisProperties customRedisProperties;

    public CacheMapRedisCacheConfiguration(CacheProperties cacheProperties, CustomRedisProperties customRedisProperties) {
        this.cacheProperties = cacheProperties;
        this.customRedisProperties = customRedisProperties;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader, RedisTemplate redisTemplate) {
        RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
        Map<String, RedisCacheConfiguration> map = getMap(resourceLoader.getClassLoader(), valueSerializer);
        // Configure the constructor to use the redisConnectionFactory as the connection factory
        // determineConfiguration is the default cacheDefaults configuration. If defaultCacheConfig is not configured, use defaultCacheConfig to configure it.
        / / withInitialCacheConfigurations using our extended configuration as the initialization configuration, if there is the initialization configuration is not the default configuration above. This serves our purpose of customizing the configuration. The expiration time of different cache domains is distinguished.
        RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(determineConfiguration(resourceLoader.getClassLoader(),valueSerializer))
            .withInitialCacheConfigurations(map);
        return builder.build();
    }
    
    // Customize the configuration
    private Map<String, RedisCacheConfiguration> getMap(ClassLoader classLoader, RedisSerializer valueSerializer) {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        customRedisProperties.getCacheNamesMap().forEach((key, value) -> {
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
            config = config.serializeValuesWith(SerializationPair.fromSerializer(valueSerializer));
            if(value.getTimeToLive() ! =null) {
                config = config.entryTtl(value.getTimeToLive());
            }
            if(value.getKeyPrefix() ! =null) {
                config = config.prefixKeysWith(value.getKeyPrefix());
            }
            if(! value.isCacheNullValues()) { config = config.disableCachingNullValues(); }if(! value.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } redisCacheConfigurationMap.put(key, config); });return redisCacheConfigurationMap;
    }
    / / that the default configuration, if there are configuration cacheProperties use it, if there is no use RedisCacheConfiguration. DefaultCacheConfig ();
    private RedisCacheConfiguration determineConfiguration(ClassLoader classLoader,RedisSerializer redisSerializer) {
        Redis redisProperties = this.cacheProperties uses it, if not.getredis (); RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); config = config.serializeValuesWith( SerializationPair.fromSerializer(redisSerializer));if(redisProperties.getTimeToLive() ! =null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if(redisProperties.getKeyPrefix() ! =null) {
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if(! redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); }if(! redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); }returnconfig; }}Copy the code

This solves the problem that all Redis caches expire at the same time based on Cacheable and allows you to configure different cache expiration times for different domains. Of course, there’s no way to be as flexible as the auto-call API, which can expire any key you want, but it does normalize cache management. Flexibility does not mean chaos, but flexibility generally translates into chaos.

The sample

Create a New SpringBoot project and introduce a Redis-starter


      
<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.1.3. RELEASE</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>
    <groupId>com.qimo</groupId>
    <artifactId>omsa-demo</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>omsa-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Copy the code

Application. The properties configuration

server.port=7777
logging.level.root=info
spring.redis.port=6379
spring.redis.host=localhost
spring.cache.cache-names[0]=user
spring.cache.cache-names[1]=order
spring.cache.custom.redis.cache-names-map.user.time-to-live=20000ms
spring.cache.custom.redis.cache-names-map.order.time-to-live=30000ms
Copy the code

Write a use case

/ * * *@Description TODO
 * @AuthorYao Zhongjie #80998699 *@Date2021/8/3 and * /
@RestController
@EnableCaching
public class CacheTest {
    @Value("${spring.cache.custom.redis.cache-names-map.user.time-to-live}")
    private String userTTL;
    @Value("${spring.cache.custom.redis.cache-names-map.order.time-to-live}")
    private String orderTTL;
    
    
    @GetMapping("/cacheUser")
    @Cacheable(cacheNames = "user")
    public String userCache(String s) {
        System.out.println("User domain cache time :"+userTTL);
        return s;
    }
    
    @GetMapping("/cacheOrder")
    @Cacheable(cacheNames = "order")
    public String orderCache(String s) {
        System.out.println("Order field cache time :"+orderTTL);
        returns; }}Copy the code

Before starting verify redis below

# redis-cli111 (127.0.0.1:6379 > TTL user: :integer) -2
127.0.0.1:6379> TTL order::111
(integer127.0.0.1) - 2:6379 >Copy the code

Start, execute, access both interfaces, and then check the expiration time as follows

111 (127.0.0.1:6379 > TTL user: :integer) 14
127.0.0.1:6379> TTL order::111
(integer6379 > 24 127.0.0.1) :Copy the code

And we’re done.