Spring Boot - 如何在开发过程中禁用 @Cacheable?

47

我正在寻找2件事情:

  1. 如何在Spring Boot“dev”配置文件中禁用所有缓存。似乎没有一个通用的设置可以在application.properties中关闭所有缓存。最简单的方法是什么?

  2. 如何禁用特定方法的缓存? 我尝试使用SpEl,例如:

  3. @Cacheable(value = "complex-calc", condition="#${spring.profiles.active} != 'dev'}")
    public String someBigCalculation(String input){
       ...
    }
    
    但我可以让它工作。SO上有几个相关的问题,但它们涉及XML配置或其他事情,而我正在使用Spring Boot 1.3.3,它使用自动配置。

    我不想使事情过于复杂。


13
иҜ·еңЁжӮЁзҡ„application-dev.propertiesж–Ү件дёӯж·»еҠ spring.cache.type=NONEгҖӮ - M. Deinum
14
spring.cache.type=NONE并不是关闭缓存,而是阻止缓存的内容。也就是说,它仍然会向您的程序添加27层AOP/拦截器堆栈,只是不进行缓存。这取决于他所说的"关闭所有"的含义。 - David Newcomb
为了忽略Cacheable中的cacheManager使用,您需要将NoOpCacheManager bean设置为主要bean。https://dev59.com/hFsV5IYBdhLWcg3wsQnf#41586253 - Amir Azizkhani
6个回答

76
默认情况下,缓存类型会被自动检测和配置。但是,您可以通过将 spring.cache.type 添加到配置中来指定要使用的缓存类型。将其值设置为 NONE 可以禁用它。
如果您想针对特定的配置文件进行设置,请将其添加到该配置文件的 application.properties 中。在本例中,修改 application-dev.properties 并添加。
spring.cache.type=NONE
这将禁用缓存。

2
使用SpEl回答我的第二个问题怎么样?在开发过程中,有一些特定的方法我不想进行缓存。 - Wouter
1
davidxxx的回答是最佳答案。它准确地解释了Spring的行为和好的解决方案。 - Nicolas Dos Santos
davidxxx的回答并没有回答如何在特定情况下在@Cacheable中使用SpEL,davidxxx的回答过于笼统...PaulNUK的回答似乎理解了这一点。 - maxxyme
1
附注:您可以将此内容放入您的application-junit配置文件中,并在所有测试中使用@ActiveProfiles("junit")。这将禁用任何测试的缓存,防止在测试运行之间保留状态(当使用@Cachable时会出现这种情况)。如果您想在一个测试中显式地测试缓存,则可以将测试类注释如下以仅在那里激活缓存:@TestPropertySource(properties = "spring.cache.type=") - membersound
值应该小写:none - user07

58

David Newcomb的评论说的是事实:

spring.cache.type=NONE并不能关闭缓存,只是防止东西被缓存。也就是说,它仍然会向您的程序添加27层AOP /拦截器堆栈,只是不会进行缓存。这取决于他所说的“关闭所有内容”的含义。

使用此选项可以加快应用程序启动速度,但也可能会产生一些开销。

1)要完全禁用Spring缓存功能

@EnableCaching类移动到专用配置类中,我们将使用@Profile来包装它以启用它:

@Profile("!dev")
@EnableCaching
@Configuration
public class CachingConfiguration {}

当然,如果您已经有一个启用了除dev环境外所有环境的Configuration类,只需重复使用即可:

@Profile("!dev")
//... any other annotation 
@EnableCaching
@Configuration
public class NoDevConfiguration {}

2)使用伪造(noop)缓存管理器

在某些情况下,通过配置文件激活@EnableCaching可能不足够,因为您的一些类或应用程序的一些Spring依赖项希望从Spring容器中检索实现org.springframework.cache.CacheManager接口的bean。
在这种情况下,正确的方法是使用一个伪造实现,该实现将允许Spring解决所有依赖关系,同时CacheManager的实现没有额外的开销。

我们可以通过使用@Bean@Profile来实现:

import org.springframework.cache.support.NoOpCacheManager; 

@Configuration
public class CacheManagerConfiguration {

    @Bean
    @Profile("!dev")
    public CacheManager getRealCacheManager() {
        return new CaffeineCacheManager(); 
        // or any other implementation
        // return new EhCacheCacheManager(); 
    }

    @Bean
    @Profile("dev")
    public CacheManager getNoOpCacheManager() {
        return new NoOpCacheManager();
    }
}

如果更适合的话,你可以添加 spring.cache.type=NONE 属性,这会产生与 M. Deinum 回答中所写的相同结果。

@Alexandar Petrov谢谢您的快速反馈。我不知道如何链接到评论,我已尽力修复了。 - davidxxx
2
选项2与指定spring.cache.type=NONE相同。 - M. Deinum
@M. Deinum 很有可能。感谢您的反馈! - davidxxx
使用@Profile并不是最佳选择,因为它要求您硬编码配置文件,而这可能有很多个。推荐的方法是使用属性来启用/禁用功能,然后可以在特定配置文件中进行配置,而无需更改代码。 - undefined

5
如果您只有一个默认配置文件,并且不想为此创建开发和生产配置文件,那么我认为这可能是您项目的一个非常快速的解决方案:
请在您的 application.properties 文件中设置以下内容:
appconfig.enablecache=true

根据您的要求,您可以将其更改为true/false

现在,在定义 @Caching Bean 时,请执行以下操作:

@Bean
public CacheManager cacheManager(@Value("${appconfig.enablecache}") String enableCaching) {
    if (enableCaching.equals("true")) {
        return new EhCacheCacheManager();
        //Add your Caching Implementation here.
    }
    return new NoOpCacheManager();
}

当属性设置为false时,将返回NoOpCacheManager(),从而有效地关闭缓存。

这是比被接受的答案更好的方法。 - undefined

4

针对您的第二个问题,可以这样处理:

编写一个方法,确定特定个人资料是否活动中(环境是您注入的 Environment)。

boolean isProfileActive(String profile) { 
   return Arrays.asList(environment.getActiveProfiles()).contains(profile);
}

然后在可缓存注释的拼写条件上使用它。

1
在任何类中,您想要全局启用或禁用缓存,在每个方法的基础上,您可以执行以下操作,这样可以让您控制类级别上如何缓存您的方法。
您可以在application.properties中使用my.cache.enabled=true进行更改。此设置当前默认为启用状态。
当然,您也可以在application-dev.properties中进行更改,并将其设置为不同的值。这种方法的优点是,如果出于某种原因需要更复杂的设置,它还可以让您按每个方法的基础进行设置。
public class MyClass {

  @Value("${my.cache.enabled:true}")
  public String isMyCacheEnabled;

  public boolean isMyCacheEnabled() {
    return (isMyCacheEnabled == null) || isMyCacheEnabled.equals("") || Boolean.valueOf(isMyCacheEnabled);
  }

  @Cacheable(
      cacheManager = "myCacheManager",
      cacheNames = "myCacheKey",
      condition = "#root.target.isMyCacheEnabled()",
      key = "#service + '-' + #key")
  public String getMyValue(String service, String key) {
    // do whatever you normally would to get your value
    return "some data" + service + key;
  }
}

0
你可以使用resilience4j-spring-boot2依赖项和org.springframework.cache.annotation.CachingConfigurer。 这个解决方案适用于最新的Spring-boot版本6.x。 对我来说有效。
以下是代码片段:

Pom.xml:

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

缓存配置类:
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类:
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配置类:

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);
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接