如何使用Java中的CacheBuilder构建和使用缓存

时间:2016-01-12 15:45:34

标签: java caching guava

我有一个方法可以提取大量数据。由于大数据集和所需的计算量,这有可能花费相当多的时间。执行此调用的方法将多次使用。结果列表每次都应返回相同的结果。话虽如此,我想缓存结果,所以我只需要进行一次计算。我应该使用CacheBuilder类。我的脚本基本上是这样的:

class CheckValidValues implements AValidValueInterface {
    private ADataSourceInterface dataSource;

    public CheckValidValues(ADataSourceInterface dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public void validate(String value) {
        List<?> validValues = dataSource.getValidValues();

        if (!validValues.contains(value)) {
            // throw an exception

所以我甚至不确定我应该在哪里放置缓存方法(即在dataSource中的CheckValidValues类或getValidValues()方法。另外,我还不完全确定如何添加代码在没有多次实例化缓存的情况下进入其中一种方法。这是我尝试采取的路线,但不知道它是否正确。在List上面添加validValues = dataSource。 getValidValues()行:

    LoadingCache<String, List<?>> validValuesCache = CacheBuilder.newBuilder()
            .expireAfterAccess(30, TimeUnit.SECONDS)
            .build(
                    new CacheLoader<String, List<?>>() {
                        public List<?> load(@Nonnull String validValues) {
                            return valuesSupplier.getValidValues();
                        }
                    }
            );

然后,我认为我可以通过以下方式获得该值:

validValuesCache.get("validValues");

我认为应该发生的是它将执行getValidValues命令并将其存储在缓存中。但是,如果多次调用此方法,那么对我来说,这意味着每次都会创建一个新的缓存。

知道我应该为此做些什么吗?我只是想将getValidValues()方法的结果添加到缓存中,以便它可以在下一次迭代中使用而无需重做任何计算。

2 个答案:

答案 0 :(得分:4)

  1. 您只想缓存单个值,即有效值列表。使用番石榴'Suppliers.memoizeWithExpiration(Supplier delegate, long duration, TimeUnit unit)

  2. 每个有效值仅存在一次。所以你的List基本上是一个Set。通过HashSet(或Guava中更有效的变体)来支持它。这样,contains()是一个哈希表查找,而不是列表中的顺序搜索。

答案 1 :(得分:3)

我们在几个项目中使用Guava和Spring-Caching,我们通过Java配置定义bean,如下所示:

@Configuration
@EnableCaching
public class GuavaCacheConfig {

    ...

    @Bean(name="CacheEnabledService")
    public SomeService someService() {
        return new CacheableSomeService();
    }

    @Bean(name="guavaCacheManager")
    public CacheManager cacheManager() {
      // if different caching strategies should occur use this technique:
      // http://www.java-allandsundry.com/2014/10/spring-caching-abstraction-and-google.html
      GuavaCacheManager guavaCacheManager = new GuavaCacheManager();
      guavaCacheManager.setCacheBuilder(cacheBuilder());
      return guavaCacheManager;
    }

    @Bean(name = "expireAfterAccessCacheBuilder")
    public CacheBuilder<Object, Object> cacheBuilder() {
      return CacheBuilder.newBuilder()
          .recordStats()
          .expireAfterAccess(5, TimeUnit.SECONDS);
    }

    @Bean(name = "keyGenerator")
    public KeyGenerator keyGenerator() {
        return new CustomKeyGenerator();
    }

    ...
}

请注意,上面的代码来自我们的一个集成测试。

应该缓存返回值的服务定义如下:

@Component
@CacheConfig(cacheNames="someCache", keyGenerator=CustomKeyGenerator.NAME, cacheManager="guavaCacheManager")
public class CacheableService {

    public final static String CACHE_NAME = "someCache";

    ...

    @Cacheable
    public <E extends BaseEntity> E findEntity(String id) {
        ...
    }

    ...

    @CachePut
    public <E extends BaseEntity> ObjectId persist(E entity) {
        ...
    }

    ...
}

由于Spring-Caching使用AOP方法,在调用@Cacheable带注释的方法时,Spring将首先检查已调用方法的缓存中是否已存在先前存储的返回值(取决于缓存键;我们因此使用自定义密钥生成器)。如果还没有值可用,Spring将调用实际的服务方法并将返回值存储到后续调用中可用的本地缓存中。

@CachePut将始终执行服务方法并将返回值放入缓存中。如果在更新的情况下缓存中的现有值应替换为新值,则此功能非常有用。