skip to Main Content

I’m setting the configuration of a Spring Data Redis Cache on application.properties using the spring.cache.redis.* keys.

However, not everything is possible to be configured on application.properties and I’d like to get a reference to the RedisCacheConfiguration created by Spring and do some further configuration on it.

From all the examples I found, it seems that this is not possible, since all of them show something like:

@Bean
RedisCacheConfiguration getRedisCacheConfiguration() {
    return RedisCacheConfiguration.defaultCacheConfig()
           .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
           .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
}

And the defaultCacheConfig method just ignores application.properties.

I also tried to get an autowired reference using:

@Bean
public RedisCacheManager getRedisCacheManager(RedisConnectionFactory connectionFactory, RedisCacheConfiguration redisCacheConfiguration) {
...

But that just results in an Exception:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.data.redis.cache.RedisCacheConfiguration' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

So, is what I want to do impossible? Should I just forget about application.properties and configure everything in code?

I’m using Spring Boot (with spring-boot-starter-cache and spring-boot-starter-data-redis) 2.7.8, Java 17 and Lettuce 6.1.10.RELEASE.

2

Answers


  1. Chosen as BEST ANSWER

    For reference, I'm posting the solution I ended up with, but John Blum's answer offers more options.

    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
        var om = jacksonObjectMapperBuilder.createXmlMapper(false).build();
        var serializer = new Jackson2JsonRedisSerializer(om.getTypeFactory().constructCollectionType(List.class, MyObject.class));
        serializer.setObjectMapper(om);
        
        return builder -> {
            builder.initialCacheNames(Set.of("mycache")); //remove this line if using spring.cache.cache-names on application.properties
            builder.getCacheConfigurationFor("mycache")
                .ifPresent(config -> builder.withCacheConfiguration("mycache", config
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))));
        };
    }
    

  2. Out of curiosity, what aspect of (Redis) cache configuration are you trying to modify (or customize)?

    Reviewing the RedisCacheConfiguration class provided by Spring Data Redis (and used by Spring Boot), most of the configuration (allow null values, key prefix, statistics, TTL) are all configurable using Spring Boot properties. Of course, the properties do not cover non-scalar/primitive type configuration, such as the key/value Redis Serializer pairs or the ConversionService used.

    First, have a look at this in the Spring Boot reference documentation.

    Declaring a RedisCacheManagerBuilderCustomizer bean in your application configuration enables you to set (and modify) the RedisCacheConfiguration for each RedisCache instance. This approach gives you complete control over individual cache configuration for each cache separately. See below.

    Of course, if you declared many different caches for different purposes across your application, then this could be tedious. In addition, if you have predetermined caches declared with the spring.cache.cache-names Spring Boot property, which might vary by environment if you are using Spring Profiled application.properties files, then this could also be problematic.

    On the other hand, even when using a large number of individual caches for different purposes, if they share common configuration then your job becomes much easier.

    For shared, common cache configuration, you were on the right track by declaring a bean of type RedisCacheConfiguration. Bean name should not matter, though I might prefer "redisCacheConfiguration" rather than "getRedisCacheConfiguration". You can even inject the Spring Boot CacheProperties instance, which Spring Boot does register as a bean in the Spring ApplicationContext.

    For example:

    @Configuration(proxyBeanMethods = false)
    class MyCustomRedisCacheConfiguration {
    
      @Bean
      RedisCacheConfiguration redisCacheConfiguration(
          CacheProperties cacheProperties,
          ConversionService conversionService) {
    
        CacheProperties.Redis redisCacheProperties = cacheProperties.getRedis();
    
        SerializationPair<String> keySerializer = RedisSerializationContext
          .SerializationPair.fromSerializer(RedisSerializer.string())
    
        SerializationPair<?> valueSerializer = RedisSerializationContext
          .SerializationPair.fromSerializer(RedisSerializer.json())
    
        RedisCacheConfiuration redisCacheConfiguration = 
          RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(redisCacheProperties.getTimeToLive())
            .prefixCacheNameWith(redisCacheProperties.getKeyPrefix())
            .serializeKeysWith(keySerializer)
            .serializeValuesWith(valueSerialier)
            .withConversionService(conversionService);
    
        if (!redisCacheProperties.isCacheNullValues()) {
          redisCacheConfiguration.disableCachingNullValues();
        }
    
        if (!redisCacheProperties.isUseKeyPrefix()) {
          redisCacheConfiguration.disableKeyPrefix();
        }
    
        return redisCacheConfiguration;
      }
    

    The (Redis) CacheProperties are the resolved properties from Spring Boot application.properties file(s), specific to caching (i.e. spring.cache.*).

    This is not unlike what Spring Boot’s auto-configuration already does (follow from here, then here and finally, here) if you do not explicitly declare a bean of type RedisCacheConfiguration.

    The default RedisCacheConfiguration provided by Spring Boot is used if you did NOT explicitly declare a bean of type RedisCacheConfiguration.

    TIP: ObjectProvider.getIfAvailable(:Supplier<?>) only invokes the Supplier if the ObjectProvider cannot provide an Object of the declared type. And, in this case, the ObjectProvider implementation would most likely be a BeanObjectProvider implementation used by Spring’s DefaultListableBeanFactory delegated to by Spring’s ApplicationContext to manage beans.

    You will also notice that Spring Boot does not register the default, provided RedisCacheConfiguration as a bean in the Spring ApplicationContext anywhere in the Spring Boot RedisCacheConfiguration class (used in auto-configuration), which is why you encountered a NoSuchBeanDefinitionException when you tried to inject what you thought was a bean when Spring Boot created the SD Redis RedisCacheConfiguration object (it doesn’t).

    Now, how to handle the individual Redis cache configuration when you need to deviate from the (default), shared RedisCacheConfiguration that might be applicable to most of your cache instances, but not all of them (perhaps)?

    This is where the RedisCacheManagerBuilderCustomizer can help!

    Again, as the documentation reference that I shared with you above describes, you must declare a bean of type RedisCacheManagerBuilderCustomizer in your application configuration.

    TIP: You can see that Spring Boot’s auto-configuration (for Redis) applies this "customizer" to the RedisCacheManager bean that gets created in auto-config. There can even be more than one, which might be useful when you enable multiple Spring Profiles and there are different customizations applied based on environment and other factors (e.g. runtime, etc).

    For example:

    @Configuration(proxyBeanMethods = false)
    class MyCustomRedisCacheConfiguration {
    
      @Bean
      RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
    
        return cacheManagerBuilder -> {
    
          cacheManagerBuilder
            .getCacheConfigurationFor("cacheRequiringIndividualCustomization")
            .ifPresent(redisCachConfiguration -> {
               
              RedisCacheConfiguation modifiedRedisCacheConfiguration = 
                redisCacheConfiguration.entryTtl(Duration.ofMinutes(5L))
                  .prefixCacheNamesWith("customCacheNamePrefix");
    
              // maybe even with different, custom key/value Serializers
    
              cacheManagerBuilder
                .withCacheConfiguration("cacheRequiringIndividualCustomization",
                  modifiedRedisCacheConfiguration);
            });
        };
      }
    }
    

    The RedisCacheManagerBuilder even allows you to specify a default RedisCacheConfiguration to use for all caches if you are not explicitly customizing all individual cache instances. The cacheDefaults(:RedisCacheConfiguration) method on RedisCacheManagerBuilder used to set defaults for all created cache instances alleviates the need for you to declare the custom RedisCacheConfiguration bean explicitly as I demonstrated first, above.

    Alright, I will leave you with this.

    Hopefully it gives you ideas and choices on how to best proceed in your particular case. Just know that Spring always has you covered. It might take a little digging, but I have yet to come across a situation that is not possible.

    Good luck!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search