Merge pull request #4903 from eddumelendez/gh-4899

* pr/4903:
  Polish contribution
  Add Caffeine cache support
pull/5289/head
Stephane Nicoll 9 years ago
commit 1f8b780333

@ -61,6 +61,11 @@
<artifactId>hal-browser</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure;
import javax.cache.Caching;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.hazelcast.core.IMap;
import com.hazelcast.spring.cache.HazelcastCache;
import net.sf.ehcache.Ehcache;
@ -26,6 +27,7 @@ import org.infinispan.spring.provider.SpringCache;
import org.springframework.boot.actuate.cache.CacheStatistics;
import org.springframework.boot.actuate.cache.CacheStatisticsProvider;
import org.springframework.boot.actuate.cache.CaffeineCacheStatisticsProvider;
import org.springframework.boot.actuate.cache.ConcurrentMapCacheStatisticsProvider;
import org.springframework.boot.actuate.cache.DefaultCacheStatistics;
import org.springframework.boot.actuate.cache.EhCacheStatisticsProvider;
@ -40,6 +42,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.ehcache.EhCacheCache;
import org.springframework.cache.guava.GuavaCache;
@ -54,6 +57,7 @@ import org.springframework.context.annotation.Configuration;
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Eddú Meléndez
* @since 1.3.0
*/
@Configuration
@ -104,6 +108,17 @@ public class CacheStatisticsAutoConfiguration {
}
@Configuration
@ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class })
static class CaffeineCacheStatisticsProviderConfiguration {
@Bean
public CaffeineCacheStatisticsProvider caffeineCacheStatisticsProvider() {
return new CaffeineCacheStatisticsProvider();
}
}
@Configuration
@ConditionalOnClass({ com.google.common.cache.Cache.class, GuavaCache.class })
static class GuavaCacheStatisticsConfiguration {

@ -0,0 +1,46 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.cache;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
/**
* {@link CacheStatisticsProvider} implementation for Caffeine.
*
* @author Eddú Meléndez
* @since 1.4.0
*/
public class CaffeineCacheStatisticsProvider
implements CacheStatisticsProvider<CaffeineCache> {
@Override
public CacheStatistics getCacheStatistics(CacheManager cacheManager,
CaffeineCache cache) {
DefaultCacheStatistics statistics = new DefaultCacheStatistics();
statistics.setSize(cache.getNativeCache().estimatedSize());
CacheStats caffeineStatistics = cache.getNativeCache().stats();
if (caffeineStatistics.requestCount() > 0) {
statistics.setHitRatio(caffeineStatistics.hitRate());
statistics.setMissRatio(caffeineStatistics.missRate());
}
return statistics;
}
}

@ -23,6 +23,7 @@ import java.util.Arrays;
import javax.cache.Caching;
import javax.cache.configuration.MutableConfiguration;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.cache.CacheBuilder;
import com.hazelcast.cache.HazelcastCachingProvider;
import com.hazelcast.config.Config;
@ -40,6 +41,7 @@ import org.springframework.boot.actuate.cache.CacheStatistics;
import org.springframework.boot.actuate.cache.CacheStatisticsProvider;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerUtils;
@ -60,6 +62,7 @@ import static org.assertj.core.api.Assertions.offset;
* Tests for {@link CacheStatisticsAutoConfiguration}.
*
* @author Stephane Nicoll
* @author Eddú Meléndez
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class CacheStatisticsAutoConfigurationTests {
@ -115,6 +118,14 @@ public class CacheStatisticsAutoConfigurationTests {
doTestCoreStatistics(provider, true);
}
@Test
public void baseCaffeineCacheStatistics() {
load(CaffeineCacheConfig.class);
CacheStatisticsProvider provider = this.context
.getBean("caffeineCacheStatisticsProvider", CacheStatisticsProvider.class);
doTestCoreStatistics(provider, true);
}
@Test
public void concurrentMapCacheStatistics() {
load(ConcurrentMapConfig.class);
@ -313,4 +324,17 @@ public class CacheStatisticsAutoConfigurationTests {
}
@Configuration
static class CaffeineCacheConfig {
@Bean
public CaffeineCacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder().recordStats());
cacheManager.setCacheNames(Arrays.asList("books", "speaker"));
return cacheManager;
}
}
}

@ -511,6 +511,11 @@
<artifactId>thymeleaf-layout-dialect</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.mxab.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-data-attribute</artifactId>

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ import org.springframework.util.Assert;
* Mappings between {@link CacheType} and {@code @Configuration}.
*
* @author Phillip Webb
* @author Eddú Meléndez
*/
final class CacheConfigurations {
@ -42,6 +43,7 @@ final class CacheConfigurations {
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
mappings.put(CacheType.GUAVA, GuavaCacheConfiguration.class);
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -44,6 +44,8 @@ public class CacheProperties {
*/
private List<String> cacheNames = new ArrayList<String>();
private final Caffeine caffeine = new Caffeine();
private final EhCache ehcache = new EhCache();
private final Hazelcast hazelcast = new Hazelcast();
@ -70,6 +72,10 @@ public class CacheProperties {
this.cacheNames = cacheNames;
}
public Caffeine getCaffeine() {
return this.caffeine;
}
public EhCache getEhcache() {
return this.ehcache;
}
@ -106,6 +112,27 @@ public class CacheProperties {
return null;
}
/**
* Caffeine specific cache properties.
*/
public static class Caffeine {
/**
* The spec to use to create caches. Check CaffeineSpec for more details on
* the spec format.
*/
private String spec;
public String getSpec() {
return this.spec;
}
public void setSpec(String spec) {
this.spec = spec;
}
}
/**
* EhCache specific cache properties.
*/

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -56,6 +56,11 @@ public enum CacheType {
*/
REDIS,
/**
* Caffeine backed caching.
*/
CAFFEINE,
/**
* Guava backed caching.
*/

@ -0,0 +1,95 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.cache;
import java.util.List;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.CaffeineSpec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Caffeine cache configuration.
*
* @author Eddú Meléndez
* @since 1.4.0
*/
@Configuration
@ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class })
@ConditionalOnMissingBean(CacheManager.class)
@Conditional({ CacheCondition.class })
class CaffeineCacheConfiguration {
@Autowired
private CacheProperties cacheProperties;
@Autowired
private CacheManagerCustomizers customizers;
@Autowired(required = false)
private Caffeine<Object, Object> caffeine;
@Autowired(required = false)
private CaffeineSpec caffeineSpec;
@Autowired(required = false)
private CacheLoader<Object, Object> cacheLoader;
@Bean
public CaffeineCacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = createCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!CollectionUtils.isEmpty(cacheNames)) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizers.customize(cacheManager);
}
private CaffeineCacheManager createCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
setCacheBuilder(cacheManager);
if (this.cacheLoader != null) {
cacheManager.setCacheLoader(this.cacheLoader);
}
return cacheManager;
}
private void setCacheBuilder(CaffeineCacheManager cacheManager) {
String specification = this.cacheProperties.getCaffeine().getSpec();
if (StringUtils.hasText(specification)) {
cacheManager.setCacheSpecification(specification);
}
else if (this.caffeineSpec != null) {
cacheManager.setCaffeineSpec(this.caffeineSpec);
}
else if (this.caffeine != null) {
cacheManager.setCaffeine(this.caffeine);
}
}
}

@ -30,6 +30,8 @@ import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.CaffeineSpec;
import com.google.common.cache.CacheBuilder;
import com.hazelcast.cache.HazelcastCachingProvider;
import com.hazelcast.core.HazelcastInstance;
@ -54,6 +56,8 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.ehcache.EhCacheCacheManager;
@ -588,6 +592,55 @@ public class CacheAutoConfigurationTests {
assertThat(((GuavaCache) foo).getNativeCache().stats().missCount()).isEqualTo(1L);
}
@Test
public void caffeineCacheWithExplicitCaches() {
load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine",
"spring.cache.cacheNames=foo");
CaffeineCacheManager cacheManager = validateCacheManager(
CaffeineCacheManager.class);
assertThat(cacheManager.getCacheNames()).containsOnly("foo");
Cache foo = cacheManager.getCache("foo");
foo.get("1");
// See next tests: no spec given so stats should be disabled
assertThat(((CaffeineCache) foo).getNativeCache().stats().missCount()).isEqualTo(0L);
}
@Test
public void caffeineCacheWithCustomizers() {
testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "caffeine",
"allCacheManagerCustomizer", "caffeineCacheManagerCustomizer");
}
@Test
public void caffeineCacheWithExplicitCacheBuilder() {
load(CaffeineCacheBuilderConfiguration.class, "spring.cache.type=caffeine",
"spring.cache.cacheNames=foo,bar");
validateCaffeineCacheWithStats();
}
@Test
public void caffeineCacheExplicitWithSpec() {
load(CaffeineCacheSpecConfiguration.class, "spring.cache.type=caffeine",
"spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar");
validateCaffeineCacheWithStats();
}
@Test
public void caffeineCacheExplicitWithSpecString() {
load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine",
"spring.cache.caffeine.spec=recordStats", "spring.cache.cacheNames[0]=foo",
"spring.cache.cacheNames[1]=bar");
validateCaffeineCacheWithStats();
}
private void validateCaffeineCacheWithStats() {
CaffeineCacheManager cacheManager = validateCacheManager(CaffeineCacheManager.class);
assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar");
Cache foo = cacheManager.getCache("foo");
foo.get("1");
assertThat(((CaffeineCache) foo).getNativeCache().stats().missCount()).isEqualTo(1L);
}
private <T extends CacheManager> T validateCacheManager(Class<T> type) {
CacheManager cacheManager = this.context.getBean(CacheManager.class);
assertThat(cacheManager).as("Wrong cache manager type").isInstanceOf(type);
@ -837,6 +890,28 @@ public class CacheAutoConfigurationTests {
}
@Configuration
@EnableCaching
static class CaffeineCacheBuilderConfiguration {
@Bean
Caffeine<Object, Object> cacheBuilder() {
return Caffeine.newBuilder().recordStats();
}
}
@Configuration
@EnableCaching
static class CaffeineCacheSpecConfiguration {
@Bean
CaffeineSpec caffeineSpec() {
return CaffeineSpec.parse("recordStats");
}
}
@Configuration
static class CacheManagerCustomizersConfiguration {
@ -888,6 +963,12 @@ public class CacheAutoConfigurationTests {
};
}
@Bean
public CacheManagerCustomizer<CaffeineCacheManager> caffeineCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<CaffeineCacheManager>() {
};
}
}
static abstract class CacheManagerTestCustomizer<T extends CacheManager>

@ -51,6 +51,7 @@
<assertj.version>2.3.0</assertj.version>
<atomikos.version>3.9.3</atomikos.version>
<bitronix.version>2.1.4</bitronix.version>
<caffeine.version>2.1.0</caffeine.version>
<cassandra-driver.version>2.1.9</cassandra-driver.version>
<commons-beanutils.version>1.9.2</commons-beanutils.version>
<commons-collections.version>3.2.2</commons-collections.version>
@ -624,6 +625,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>com.github.mxab.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-data-attribute</artifactId>

@ -63,6 +63,7 @@ content into your application; rather pick only the properties that you need.
# SPRING CACHE ({sc-spring-boot-autoconfigure}/cache/CacheProperties.{sc-ext}[CacheProperties])
spring.cache.cache-names= # Comma-separated list of cache names to create if supported by the underlying cache manager.
spring.cache.caffeine.spec= # The spec to use to create caches. Check CaffeineSpec for more details on the spec format.
spring.cache.ehcache.config= # The location of the configuration file to use to initialize EhCache.
spring.cache.guava.spec= # The spec to use to create caches. Check CacheBuilderSpec for more details on the spec format.
spring.cache.hazelcast.config= # The location of the configuration file to use to initialize Hazelcast.

@ -3249,6 +3249,7 @@ Spring Boot tries to detect the following providers (in this order):
* <<boot-features-caching-provider-hazelcast,Hazelcast>>
* <<boot-features-caching-provider-infinispan,Infinispan>>
* <<boot-features-caching-provider-redis,Redis>>
* <<boot-features-caching-provider-caffeine,Caffeine>>
* <<boot-features-caching-provider-guava,Guava>>
* <<boot-features-caching-provider-simple,Simple>>
@ -3384,6 +3385,31 @@ recommend to keep this setting enabled if you create your own `RedisCacheManager
[[boot-features-caching-provider-caffeine]]
==== Caffeine
Caffeine is a Java 8 rewrite of Guavas cache and will supersede the Guava support in
Spring Boot 2.0. If Caffeine is present, a `CaffeineCacheManager` is auto-configured.
Caches can be created on startup using the `spring.cache.cache-names` property and
customized by one of the following (in this order):
1. A cache spec defined by `spring.cache.caffeine.spec`
2. A `com.github.benmanes.caffeine.cache.CaffeineSpec` bean is defined
3. A `com.github.benmanes.caffeine.cache.Caffeine` bean is defined
For instance, the following configuration creates a `foo` and `bar` caches with a maximum
size of 500 and a _time to live_ of 10 minutes
[source,properties,indent=0]
----
spring.cache.cache-names=foo,bar
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
----
Besides, if a `com.github.benmanes.caffeine.cache.CacheLoader` bean is defined, it is
automatically associated to the `CaffeineCacheManager`.
[[boot-features-caching-provider-guava]]
==== Guava
If Guava is present, a `GuavaCacheManager` is auto-configured. Caches can be created

@ -84,6 +84,13 @@ a redis instance with the default settings is expected on your local box).
=== Caffeine
Simply add the `com.github.ben-manes.caffeine:caffeine` dependency to enable support
for Caffeine. You can customize how caches are created in different ways, see
`application.properties` for an example and the documentation for more details.
=== Guava
Spring Boot does not provide any dependency management for _Guava_ so you'll have to add
the `com.google.guava:guava` dependency with a version. You can customize how caches are

@ -10,6 +10,12 @@
#spring.cache.jcache.config=hazelcast.xml
#
# Caffeine configuration
#
#spring.cache.caffeine.spec=maximumSize=200,expireAfterAccess=600s
#
# Guava configuration
#

Loading…
Cancel
Save