Merge pull request #921 from ben-manes/caffeine

* pr/921:
  Polish contribution
  Add caching support for Caffeine
  Detect @Autowired constructors for configuration classes
This commit is contained in:
Stephane Nicoll 2015-12-18 15:47:33 +01:00
commit 1502e49fa5
10 changed files with 692 additions and 9 deletions

View File

@ -28,6 +28,7 @@ configure(allprojects) { project ->
version = qualifyVersionIfNecessary(version)
ext.aspectjVersion = "1.8.7"
ext.caffeineVersion = "2.0.1"
ext.eclipselinkVersion = "2.4.2"
ext.ehcacheVersion = "2.10.1"
ext.ehcachejcacheVersion = "1.0.1"
@ -647,6 +648,7 @@ project("spring-context-support") {
optional("javax.mail:javax.mail-api:${javamailVersion}")
optional("javax.cache:cache-api:1.0.0")
optional("com.google.guava:guava:${guavaVersion}")
optional("com.github.ben-manes.caffeine:caffeine:${caffeineVersion}")
optional("net.sf.ehcache:ehcache:${ehcacheVersion}")
optional("org.quartz-scheduler:quartz:2.2.2")
optional("org.codehaus.fabric3.api:commonj:1.1.0")

View File

@ -271,6 +271,19 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
Constructor<?> defaultConstructor = null;
for (Constructor<?> candidate : rawCandidates) {
AnnotationAttributes ann = findAutowiredAnnotation(candidate);
if (ann == null) {
Class<?> userClass = ClassUtils.getUserClass(beanClass);
if (userClass != beanClass) {
try {
Constructor<?> superCtor =
userClass.getDeclaredConstructor(candidate.getParameterTypes());
ann = findAutowiredAnnotation(superCtor);
}
catch (NoSuchMethodException ex) {
// Simply proceed, no equivalent superclass constructor found...
}
}
}
if (ann != null) {
if (requiredConstructor != null) {
throw new BeanCreationException(beanName,

View File

@ -0,0 +1,136 @@
/*
* Copyright 2002-2015 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.cache.caffeine;
import java.util.function.Function;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.util.Assert;
/**
* Spring {@link org.springframework.cache.Cache} adapter implementation
* on top of a Caffeine {@link com.github.benmanes.caffeine.cache.Cache} instance.
*
* <p>Requires Caffeine 2.0 or higher.
*
* @author Ben Manes
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 4.3
*/
public class CaffeineCache extends AbstractValueAdaptingCache {
private final String name;
private final com.github.benmanes.caffeine.cache.Cache<Object, Object> cache;
/**
* Create a {@link CaffeineCache} instance with the specified name and the
* given internal {@link com.github.benmanes.caffeine.cache.Cache} to use.
* @param name the name of the cache
* @param cache the backing Caffeine Cache instance
*/
public CaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache) {
this(name, cache, true);
}
/**
* Create a {@link CaffeineCache} instance with the specified name and the
* given internal {@link com.github.benmanes.caffeine.cache.Cache} to use.
* @param name the name of the cache
* @param cache the backing Caffeine Cache instance
* @param allowNullValues whether to accept and convert {@code null}
* values for this cache
*/
public CaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache,
boolean allowNullValues) {
super(allowNullValues);
Assert.notNull(name, "Name must not be null");
Assert.notNull(cache, "Cache must not be null");
this.name = name;
this.cache = cache;
}
@Override
public final String getName() {
return this.name;
}
@Override
public final com.github.benmanes.caffeine.cache.Cache<Object, Object> getNativeCache() {
return this.cache;
}
@Override
public ValueWrapper get(Object key) {
if (this.cache instanceof LoadingCache) {
Object value = ((LoadingCache<Object, Object>) this.cache).get(key);
return toValueWrapper(value);
}
return super.get(key);
}
@Override
protected Object lookup(Object key) {
return this.cache.getIfPresent(key);
}
@Override
public void put(Object key, Object value) {
this.cache.put(key, toStoreValue(value));
}
@Override
public ValueWrapper putIfAbsent(Object key, final Object value) {
PutIfAbsentFunction callable = new PutIfAbsentFunction(value);
Object result = this.cache.get(key, callable);
return (callable.called ? null : toValueWrapper(result));
}
@Override
public void evict(Object key) {
this.cache.invalidate(key);
}
@Override
public void clear() {
this.cache.invalidateAll();
}
private class PutIfAbsentFunction implements Function<Object, Object> {
private final Object value;
private boolean called;
public PutIfAbsentFunction(Object value) {
this.value = value;
}
@Override
public Object apply(Object key) {
this.called = true;
return toStoreValue(this.value);
}
}
}

View File

@ -0,0 +1,206 @@
/*
* Copyright 2002-2015 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.cache.caffeine;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link CacheManager} implementation that lazily builds {@link CaffeineCache}
* instances for each {@link #getCache} request. Also supports a 'static' mode
* where the set of cache names is pre-defined through {@link #setCacheNames},
* with no dynamic creation of further cache regions at runtime.
*
* <p>The configuration of the underlying cache can be fine-tuned through a
* {@link Caffeine} builder, passed into this CacheManager through
* {@link #setCaffeine}.
*
* <p>Requires Caffeine 2.0 or higher.
*
* @author Ben Manes
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 4.3
* @see CaffeineCache
*/
public class CaffeineCacheManager implements CacheManager {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
private boolean dynamic = true;
private Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
private CacheLoader<Object, Object> cacheLoader;
private boolean allowNullValues = true;
/**
* Construct a dynamic CaffeineCacheManager,
* lazily creating cache instances as they are being requested.
*/
public CaffeineCacheManager() {
}
/**
* Construct a static CaffeineCacheManager,
* managing caches for the specified cache names only.
*/
public CaffeineCacheManager(String... cacheNames) {
setCacheNames(Arrays.asList(cacheNames));
}
/**
* Specify the set of cache names for this CacheManager's 'static' mode.
* <p>The number of caches and their names will be fixed after a call to this method,
* with no creation of further cache regions at runtime.
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
*/
public void setCacheNames(Collection<String> cacheNames) {
if (cacheNames != null) {
for (String name : cacheNames) {
this.cacheMap.put(name, createCaffeineCache(name));
}
this.dynamic = false;
}
else {
this.dynamic = true;
}
}
/**
* Set the Caffeine to use for building each individual
* {@link CaffeineCache} instance.
* @see #createNativeCaffeineCache
* @see com.github.benmanes.caffeine.cache.Caffeine#build()
*/
public void setCaffeine(Caffeine<Object, Object> cacheBuilder) {
Assert.notNull(cacheBuilder, "Caffeine must not be null");
doSetCaffeine(cacheBuilder);
}
/**
* Set the Caffeine CacheLoader to use for building each individual
* {@link CaffeineCache} instance, turning it into a LoadingCache.
* @see #createNativeCaffeineCache
* @see com.github.benmanes.caffeine.cache.Caffeine#build(CacheLoader)
* @see com.github.benmanes.caffeine.cache.LoadingCache
*/
public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) {
this.cacheLoader = cacheLoader;
refreshKnownCaches();
}
}
/**
* Specify whether to accept and convert {@code null} values for all caches
* in this cache manager.
* <p>Default is "true", despite Caffeine itself not supporting {@code null} values.
* An internal holder object will be used to store user-level {@code null}s.
*/
public void setAllowNullValues(boolean allowNullValues) {
if (this.allowNullValues != allowNullValues) {
this.allowNullValues = allowNullValues;
refreshKnownCaches();
}
}
/**
* Return whether this cache manager accepts and converts {@code null} values
* for all of its caches.
*/
public boolean isAllowNullValues() {
return this.allowNullValues;
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createCaffeineCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
/**
* Create a new CaffeineCache instance for the specified cache name.
* @param name the name of the cache
* @return the Spring CaffeineCache adapter (or a decorator thereof)
*/
protected Cache createCaffeineCache(String name) {
return new CaffeineCache(name, createNativeCaffeineCache(name), isAllowNullValues());
}
/**
* Create a native Caffeine Cache instance for the specified cache name.
* @param name the name of the cache
* @return the native Caffeine Cache instance
*/
protected com.github.benmanes.caffeine.cache.Cache<Object, Object> createNativeCaffeineCache(String name) {
if (this.cacheLoader != null) {
return this.cacheBuilder.build(this.cacheLoader);
}
else {
return this.cacheBuilder.build();
}
}
private void doSetCaffeine(Caffeine<Object, Object> cacheBuilder) {
if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) {
this.cacheBuilder = cacheBuilder;
refreshKnownCaches();
}
}
/**
* Create the known caches again with the current state of this manager.
*/
private void refreshKnownCaches() {
for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
entry.setValue(createCaffeineCache(entry.getKey()));
}
}
}

View File

@ -0,0 +1,6 @@
/**
* Support classes for the open source cache in
* <a href="https://github.com/ben-manes/caffeine/">Caffeine</a> library,
* allowing to set up Caffeine caches within Spring's cache abstraction.
*/
package org.springframework.cache.caffeine;

View File

@ -0,0 +1,186 @@
/*
* Copyright 2002-2015 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.cache.caffeine;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Ben Manes
* @author Juergen Hoeller
* @author Stephane Nicoll
*/
public class CaffeineCacheManagerTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void testDynamicMode() {
CacheManager cm = new CaffeineCacheManager();
Cache cache1 = cm.getCache("c1");
assertTrue(cache1 instanceof CaffeineCache);
Cache cache1again = cm.getCache("c1");
assertSame(cache1again, cache1);
Cache cache2 = cm.getCache("c2");
assertTrue(cache2 instanceof CaffeineCache);
Cache cache2again = cm.getCache("c2");
assertSame(cache2again, cache2);
Cache cache3 = cm.getCache("c3");
assertTrue(cache3 instanceof CaffeineCache);
Cache cache3again = cm.getCache("c3");
assertSame(cache3again, cache3);
cache1.put("key1", "value1");
assertEquals("value1", cache1.get("key1").get());
cache1.put("key2", 2);
assertEquals(2, cache1.get("key2").get());
cache1.put("key3", null);
assertNull(cache1.get("key3").get());
cache1.evict("key3");
assertNull(cache1.get("key3"));
}
@Test
public void testStaticMode() {
CaffeineCacheManager cm = new CaffeineCacheManager("c1", "c2");
Cache cache1 = cm.getCache("c1");
assertTrue(cache1 instanceof CaffeineCache);
Cache cache1again = cm.getCache("c1");
assertSame(cache1again, cache1);
Cache cache2 = cm.getCache("c2");
assertTrue(cache2 instanceof CaffeineCache);
Cache cache2again = cm.getCache("c2");
assertSame(cache2again, cache2);
Cache cache3 = cm.getCache("c3");
assertNull(cache3);
cache1.put("key1", "value1");
assertEquals("value1", cache1.get("key1").get());
cache1.put("key2", 2);
assertEquals(2, cache1.get("key2").get());
cache1.put("key3", null);
assertNull(cache1.get("key3").get());
cache1.evict("key3");
assertNull(cache1.get("key3"));
cm.setAllowNullValues(false);
Cache cache1x = cm.getCache("c1");
assertTrue(cache1x instanceof CaffeineCache);
assertTrue(cache1x != cache1);
Cache cache2x = cm.getCache("c2");
assertTrue(cache2x instanceof CaffeineCache);
assertTrue(cache2x != cache2);
Cache cache3x = cm.getCache("c3");
assertNull(cache3x);
cache1x.put("key1", "value1");
assertEquals("value1", cache1x.get("key1").get());
cache1x.put("key2", 2);
assertEquals(2, cache1x.get("key2").get());
try {
cache1x.put("key3", null);
fail("Should have thrown NullPointerException");
}
catch (NullPointerException ex) {
// expected
}
cm.setAllowNullValues(true);
Cache cache1y = cm.getCache("c1");
cache1y.put("key3", null);
assertNull(cache1y.get("key3").get());
cache1y.evict("key3");
assertNull(cache1y.get("key3"));
}
@Test
public void changeCacheSpecificationRecreateCache() {
CaffeineCacheManager cm = new CaffeineCacheManager("c1");
Cache cache1 = cm.getCache("c1");
Caffeine<Object, Object> caffeine = Caffeine.newBuilder().maximumSize(10);
cm.setCaffeine(caffeine);
Cache cache1x = cm.getCache("c1");
assertTrue(cache1x != cache1);
cm.setCaffeine(caffeine); // Set same instance
Cache cache1xx = cm.getCache("c1");
assertSame(cache1x, cache1xx);
}
@Test
public void changeCacheLoaderRecreateCache() {
CaffeineCacheManager cm = new CaffeineCacheManager("c1");
Cache cache1 = cm.getCache("c1");
CacheLoader<Object, Object> loader = mockCacheLoader();
cm.setCacheLoader(loader);
Cache cache1x = cm.getCache("c1");
assertTrue(cache1x != cache1);
cm.setCacheLoader(loader); // Set same instance
Cache cache1xx = cm.getCache("c1");
assertSame(cache1x, cache1xx);
}
@Test
public void setCacheNameNullRestoreDynamicMode() {
CaffeineCacheManager cm = new CaffeineCacheManager("c1");
assertNull(cm.getCache("someCache"));
cm.setCacheNames(null);
assertNotNull(cm.getCache("someCache"));
}
@Test
public void cacheLoaderUseLoadingCache() {
CaffeineCacheManager cm = new CaffeineCacheManager("c1");
cm.setCacheLoader(new CacheLoader<Object, Object>() {
@Override
public Object load(Object key) throws Exception {
if ("ping".equals(key)) {
return "pong";
}
throw new IllegalArgumentException("I only know ping");
}
});
Cache cache1 = cm.getCache("c1");
Cache.ValueWrapper value = cache1.get("ping");
assertNotNull(value);
assertEquals("pong", value.get());
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("I only know ping");
assertNull(cache1.get("foo"));
}
@SuppressWarnings("unchecked")
private CacheLoader<Object, Object> mockCacheLoader() {
return mock(CacheLoader.class);
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2002-2015 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.cache.caffeine;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cache.AbstractCacheTests;
import org.springframework.cache.Cache;
import static org.junit.Assert.*;
/**
* @author Ben Manes
* @author Stephane Nicoll
*/
public class CaffeineCacheTests extends AbstractCacheTests<CaffeineCache> {
private com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache;
private CaffeineCache cache;
@Before
public void setUp() {
nativeCache = Caffeine.newBuilder().build();
cache = new CaffeineCache(CACHE_NAME, nativeCache);
}
@Override
protected CaffeineCache getCache() {
return cache;
}
@Override
protected Object getNativeCache() {
return nativeCache;
}
@Test
public void putIfAbsentNullValue() throws Exception {
CaffeineCache cache = getCache();
Object key = new Object();
Object value = null;
assertNull(cache.get(key));
assertNull(cache.putIfAbsent(key, value));
assertEquals(value, cache.get(key).get());
Cache.ValueWrapper wrapper = cache.putIfAbsent(key, "anotherValue");
assertNotNull(wrapper); // A value is set but is 'null'
assertEquals(null, wrapper.get());
assertEquals(value, cache.get(key).get()); // not changed
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@ -35,8 +35,10 @@ import org.springframework.cache.CacheManager;
* <p>Note: This is by no means a sophisticated CacheManager; it comes with no
* cache configuration options. However, it may be useful for testing or simple
* caching scenarios. For advanced local caching needs, consider
* {@link org.springframework.cache.guava.GuavaCacheManager} or
* {@link org.springframework.cache.ehcache.EhCacheCacheManager}.
* {@link org.springframework.cache.jcache.JCacheCacheManager},
* {@link org.springframework.cache.ehcache.EhCacheCacheManager},
* {@link com.github.benmanes.caffeine.cache.CaffeineCacheManager} or
* {@link org.springframework.cache.guava.GuavaCacheManager}.
*
* @author Juergen Hoeller
* @since 3.1

View File

@ -23,7 +23,6 @@ import javax.inject.Provider;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -90,7 +89,7 @@ public class AutowiredConfigurationTests {
}
@Test
public void testAutowiredConfigurationConstructorsAreSupported() {
public void testAutowiredSingleConstructorSupported() {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(factory).loadBeanDefinitions(
new ClassPathResource("annotation-config.xml", AutowiredConstructorConfig.class));
@ -98,8 +97,19 @@ public class AutowiredConfigurationTests {
ctx.registerBeanDefinition("config1", new RootBeanDefinition(AutowiredConstructorConfig.class));
ctx.registerBeanDefinition("config2", new RootBeanDefinition(ColorConfig.class));
ctx.refresh();
assertSame(ctx.getBean(AutowiredConstructorConfig.class).colour,
ctx.getBean(Colour.class));
assertSame(ctx.getBean(AutowiredConstructorConfig.class).colour, ctx.getBean(Colour.class));
}
@Test
public void testAutowiredAnnotatedConstructorSupported() {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(factory).loadBeanDefinitions(
new ClassPathResource("annotation-config.xml", MultipleConstructorConfig.class));
GenericApplicationContext ctx = new GenericApplicationContext(factory);
ctx.registerBeanDefinition("config1", new RootBeanDefinition(MultipleConstructorConfig.class));
ctx.registerBeanDefinition("config2", new RootBeanDefinition(ColorConfig.class));
ctx.refresh();
assertSame(ctx.getBean(MultipleConstructorConfig.class).colour, ctx.getBean(Colour.class));
}
@Test
@ -223,13 +233,29 @@ public class AutowiredConfigurationTests {
Colour colour;
@Autowired
// @Autowired
AutowiredConstructorConfig(Colour colour) {
this.colour = colour;
}
}
@Configuration
static class MultipleConstructorConfig {
Colour colour;
@Autowired
MultipleConstructorConfig(Colour colour) {
this.colour = colour;
}
MultipleConstructorConfig(String test) {
this.colour = new Colour(test);
}
}
@Configuration
static class ColorConfig {

View File

@ -8160,6 +8160,7 @@ materialized by the `org.springframework.cache.Cache` and
There are <<cache-store-configuration,a few implementations>> of that abstraction
available out of the box: JDK `java.util.concurrent.ConcurrentMap` based caches,
http://ehcache.org/[EhCache], Gemfire cache,
https://github.com/ben-manes/caffeine/wiki[Caffeine],
https://code.google.com/p/guava-libraries/wiki/CachesExplained[Guava caches] and
JSR-107 compliant caches. See <<cache-plug>> for more information on plugging in
other cache stores/providers.
@ -8996,7 +8997,7 @@ we did in the example above by defining the target cache through the `cache:defi
[[cache-store-configuration]]
=== Configuring the cache storage
Out of the box, the cache abstraction provides several storages integration. To use
Out of the box, the cache abstraction provides several storage integration. To use
them, one needs to simply declare an appropriate `CacheManager` - an entity that
controls and manages ++Cache++s and can be used to retrieve these for storage.
@ -9054,6 +9055,42 @@ This setup bootstraps the ehcache library inside Spring IoC (through the `ehcach
is then wired into the dedicated `CacheManager` implementation. Note the entire
ehcache-specific configuration is read from `ehcache.xml`.
[[cache-store-configuration-caffeine]]
==== Caffeine Cache
Caffeine is a Java 8 rewrite of Guava's cache and its implementation is located under
`org.springframework.cache.caffeine` package and provides access to several features
of Caffeine.
Configuring a `CacheManager` that creates the cache on demand is straightforward:
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<bean id="cacheManager"
class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
----
It is also possible to provide the caches to use explicitly. In that case, only those
will be made available by the manager:
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
<property name="caches">
<set>
<value>default</value>
<value>books</value>
</set>
</property>
</bean>
----
The Caffeine `CacheManager` also supports customs `Caffeine` and `CacheLoader`. See
the https://github.com/ben-manes/caffeine/wiki[Caffeine documentation] for more
information about those.
[[cache-store-configuration-guava]]
==== Guava Cache