How to disable Redis cache with Spring Boot?
Asked Answered
S

3

9

With Spring Boot 2.1 I am defining a RedisCacheManager bean in a configuration file, with Java configuration. Everything works correctly but I would like sometimes to disable it, for instance in the tests. Spring Boot provides the spring.cache.type=NONE to disable caching, as per this documentation. However, this property will not work because I already define a CacheManager, and as such Spring Boot will not configure the NoOpCacheManager I would like there (there is a @ConditionalOnMissingBean(CacheManager.class) on the NoOpCacheConfiguration which has lower precedence than RedisCacheConfiguration).

When defining caches, whatever the provider (for intance Caffeine), we usually define them as beans, which are afterwards resolved by Spring Boot's auto-configuration into a SimpleCacheManager. For instance

    @Bean
    public Cache myCache() {
        return new CaffeineCache(
                "my-cache",
                Caffeine.newBuilder()
                        .maximumSize(10)
                        .build());
    }

Unfortunately this is not possible with Redis, because its Cache implementation, RedisCache, is not public.

Another thing we like to do is to define a bean CacheManagerCustomizer<?>, for instance with Caffeine

    @Bean
    public CacheManagerCustomizer<CaffeineCacheManager> caffeineCacheManager() {
        return cacheManager -> cacheManager
                .setCaffeine(Caffeine.newBuilder()
                        .expireAfterWrite(1, TimeUnit.MINUTES));
    }

Again this is not possible with Redis, as RedisCacheManager is immutable.

So the only solution right now is to create our own RedisCacheManager, but this prevent the usage of spring.cache.type: NONE.

So here is my question. What is the best way to configure a Redis cache with Spring Boot so that we can disable it as needed ?

Scotopia answered 9/5, 2019 at 11:9 Comment(1)
you can ignore every Bean that you don't need in test profile with @Profile("!test")Hartsell
P
15

I had a requirement to enable/disable Redis auto configuration from spring.cache.type property. The below code solved my issue. This might help for anyone who want to disable/enable redis just by changing single property, in my case it is spring.cache.type=redis. Below are the major configurations.

@SpringBootApplication(exclude = {RedisAutoConfiguration.class})
public class SpringBootApp extends SpringBootServletInitializer {

}
@ConditionalOnProperty(prefix = "spring", name = "cache.type", havingValue = "redis")
@Configuration
@Import({ RedisAutoConfiguration.class })
public class ApplicationRedisConfig {

}

To enable auto configuration from application.yaml

spring:
  cache:
    type: redis
  redis.host: redis

Health check gives below response when redis is not available which shows that auto configuration has been included.

{
  "status": "DOWN",
  "details": {
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 486730272768,
        "free": 216405499904,
        "threshold": 10485760
      }
    },
    "db": {
      "status": "UP",
      "details": {
        "database": "PostgreSQL",
        "hello": 1
      }
    },
    "elasticsearch": {
      "status": "UP"
    },
    "redis": {
      "status": "DOWN",
      "details": {
        "error": "org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to redis:6379"
      }
    }
  }
}

To disable auto configuration from application.yaml

spring:
  cache:
    type: simple
  redis.host: redis

Health check gives below response which shows redis has been excluded from auto configuration.

{
  "status": "UP",
  "details": {
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 486730272768,
        "free": 215928782848,
        "threshold": 10485760
      }
    },
    "db": {
      "status": "UP",
      "details": {
        "database": "PostgreSQL",
        "hello": 1
      }
    },
    "elasticsearch": {
      "status": "UP"
    }
  }
}
Packing answered 18/2, 2021 at 22:2 Comment(1)
very clean and straightforward solution, thank you!Hokusai
A
0

exclude attribute of the @SpringBootApplication annotation, something like: @SpringBootApplication( exclude = { RedisAutoConfiguration.class } )

and set: spring.data.redis.repositories.enabled=false

Acrylonitrile answered 9/5, 2019 at 11:13 Comment(2)
I would prefer not to rely on configuration exclusion, since it might break parts of our application. For instance if we want to inject the RedisCacheManager somewhere (for whatever reason), the application would fail. spring.cache.type: NONE let us do thatScotopia
Yes, thanks. So finally, I added 3 properties to make it work: spring.cache.type: simple, spring.data.redis.repositories.enabled: false, spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration. Also I added a CacheManager bean to test config to let the cache be configured (as well as adding @ConditionalOnProperty(name = "redis.host") to the production config)Varney
A
0

You can use resilience4j-spring-boot2 dependency with org.springframework.cache.annotation.CachingConfigurer. this solution will work with the latest Spring-boot version 6.x. it worked for me. Below are code snippet:

Pom.xml:

<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
 <version>2.1.0</version>
</dependency>
    

Cache config class:

package com.redis.sample;
    import org.springframework.cache.annotation.CachingConfigurer;
    import org.springframework.cache.interceptor.CacheErrorHandler;
    import org.springframework.context.annotation.Configuration;

    @Configuration
    public class CachingConfiguration implements CachingConfigurer        {  
        @Override
        public CacheErrorHandler errorHandler() {
            return new CustomCacheErrorHandler();
        }
    }

CustomCacheErrorHandler class:

package com.redis.sample;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.stereotype.Component;

@Component
public class CustomCacheErrorHandler implements CacheErrorHandler {
    
      
    
     
    @Override
    public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
        //do something as per usecase
    }

    @Override
    public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
        //do something as per usecase
    }

    @Override
    public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
        //do something as per usecase
    }

    @Override
    public void handleCacheClearError(RuntimeException exception, Cache cache) {
        //do something as per usecase
    }
}

CircuitBreakerConfig.java config class package com.redis.sample;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
@Configuration
public class CircuitBreakerConfig {

    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry() {
        io.github.resilience4j.circuitbreaker.CircuitBreakerConfig config =  io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.custom()
                .failureRateThreshold(6)
                .permittedNumberOfCallsInHalfOpenState(2)
                .slidingWindowSize(5)
                .minimumNumberOfCalls(5)
                .build();
        return CircuitBreakerRegistry.of(config);
    }
    
     
    @Bean
     public CircuitBreaker defaultCircuitBreaker() {
        io.github.resilience4j.circuitbreaker.CircuitBreakerConfig config = io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.custom()
                .minimumNumberOfCalls(2)
                .build();
        return CircuitBreaker.of("default", config);
     }
}
Abysm answered 13/11, 2023 at 13:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.