Create spring-boot-cache module

This commit is contained in:
Stéphane Nicoll
2025-03-30 09:43:37 +02:00
committed by Phillip Webb
parent dbd5c9847d
commit 63255f3c62
62 changed files with 206 additions and 120 deletions

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import org.cache2k.Cache2kBuilder;
/**
* Callback interface that can be implemented by beans wishing to customize the default
* setup for caches added to the manager through addCaches and for dynamically created
* caches.
*
* @author Jens Wilke
* @author Stephane Nicoll
* @since 4.0.0
*/
public interface Cache2kBuilderCustomizer {
/**
* Customize the default cache settings.
* @param builder the builder to customize
*/
void customize(Cache2kBuilder<?, ?> builder);
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.Collection;
import java.util.function.Function;
import org.cache2k.Cache2kBuilder;
import org.cache2k.extra.spring.SpringCache2kCacheManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
/**
* Cache2k cache configuration.
*
* @author Jens Wilke
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Cache2kBuilder.class, SpringCache2kCacheManager.class })
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class Cache2kCacheConfiguration {
@Bean
SpringCache2kCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers,
ObjectProvider<Cache2kBuilderCustomizer> cache2kBuilderCustomizers) {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager();
cacheManager.defaultSetup(configureDefaults(cache2kBuilderCustomizers));
Collection<String> cacheNames = cacheProperties.getCacheNames();
if (!CollectionUtils.isEmpty(cacheNames)) {
cacheManager.setDefaultCacheNames(cacheNames);
}
return customizers.customize(cacheManager);
}
private Function<Cache2kBuilder<?, ?>, Cache2kBuilder<?, ?>> configureDefaults(
ObjectProvider<Cache2kBuilderCustomizer> cache2kBuilderCustomizers) {
return (builder) -> {
cache2kBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
};
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.cache.CacheType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration.CacheConfigurationImportSelector;
import org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jpa.autoconfigure.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.util.Assert;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the cache abstraction. Creates a
* {@link CacheManager} if necessary when caching is enabled via
* {@link EnableCaching @EnableCaching}.
* <p>
* Cache store can be auto-detected or specified explicitly through configuration.
*
* @author Stephane Nicoll
* @since 4.0.0
* @see EnableCaching
*/
@AutoConfiguration(afterName = { "org.springframework.boot.data.couchbase.autoconfigure.CouchbaseDataAutoConfiguration",
"org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration",
"org.springframework.boot.hazelcast.autoconfigure.HazelcastAutoConfiguration",
"org.springframework.boot.jpa.autoconfigure.hibernate.HibernateJpaAutoConfiguration" })
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnConfiguration.class })
public class CacheAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
return new CacheManagerCustomizers(customizers.orderedStream().toList());
}
@Bean
public CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties,
ObjectProvider<CacheManager> cacheManager) {
return new CacheManagerValidator(cacheProperties, cacheManager);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ EntityManagerFactoryDependsOnPostProcessor.class,
LocalContainerEntityManagerFactoryBean.class })
@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
@Import(CacheManagerEntityManagerFactoryDependsOnPostProcessor.class)
static class CacheManagerEntityManagerFactoryDependsOnConfiguration {
}
static class CacheManagerEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
CacheManagerEntityManagerFactoryDependsOnPostProcessor() {
super("cacheManager");
}
}
/**
* Bean used to validate that a CacheManager exists and provide a more meaningful
* exception.
*/
static class CacheManagerValidator implements InitializingBean {
private final CacheProperties cacheProperties;
private final ObjectProvider<CacheManager> cacheManager;
CacheManagerValidator(CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {
this.cacheProperties = cacheProperties;
this.cacheManager = cacheManager;
}
@Override
public void afterPropertiesSet() {
Assert.state(this.cacheManager.getIfAvailable() != null,
() -> "No cache manager could be auto-configured, check your configuration (caching type is '"
+ this.cacheProperties.getType() + "')");
}
}
/**
* {@link ImportSelector} to add {@link CacheType} configuration classes.
*/
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import org.springframework.boot.autoconfigure.cache.CacheType;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
/**
* General cache condition used with all cache configuration classes.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Madhura Bhave
*/
class CacheCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sourceClass = "";
if (metadata instanceof ClassMetadata classMetadata) {
sourceClass = classMetadata.getClassName();
}
ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", sourceClass);
Environment environment = context.getEnvironment();
try {
BindResult<CacheType> specified = Binder.get(environment).bind("spring.cache.type", CacheType.class);
if (!specified.isBound()) {
return ConditionOutcome.match(message.because("automatic cache type"));
}
CacheType required = CacheConfigurations.getType(((AnnotationMetadata) metadata).getClassName());
if (specified.get() == required) {
return ConditionOutcome.match(message.because(specified.get() + " cache type"));
}
}
catch (BindException ex) {
// Ignore
}
return ConditionOutcome.noMatch(message.because("unknown cache type"));
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import org.springframework.boot.autoconfigure.cache.CacheType;
import org.springframework.util.Assert;
/**
* Mappings between {@link CacheType} and {@code @Configuration}.
*
* @author Phillip Webb
* @author Eddú Meléndez
* @author Sebastien Deleuze
*/
final class CacheConfigurations {
private static final Map<CacheType, String> MAPPINGS;
static {
Map<CacheType, String> mappings = new EnumMap<>(CacheType.class);
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName());
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName());
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName());
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName());
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName());
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName());
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName());
mappings.put(CacheType.CACHE2K, Cache2kCacheConfiguration.class.getName());
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName());
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName());
MAPPINGS = Collections.unmodifiableMap(mappings);
}
private CacheConfigurations() {
}
static String getConfigurationClass(CacheType cacheType) {
String configurationClassName = MAPPINGS.get(cacheType);
Assert.state(configurationClassName != null, () -> "Unknown cache type " + cacheType);
return configurationClassName;
}
static CacheType getType(String configurationClassName) {
for (Map.Entry<CacheType, String> entry : MAPPINGS.entrySet()) {
if (entry.getValue().equals(configurationClassName)) {
return entry.getKey();
}
}
throw new IllegalStateException("Unknown configuration class " + configurationClassName);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import org.springframework.cache.CacheManager;
/**
* Callback interface that can be implemented by beans wishing to customize the cache
* manager before it is fully initialized, in particular to tune its configuration.
*
* @param <T> the type of the {@link CacheManager}
* @author Stephane Nicoll
* @since 4.0.0
*/
@FunctionalInterface
public interface CacheManagerCustomizer<T extends CacheManager> {
/**
* Customize the cache manager.
* @param cacheManager the {@code CacheManager} to customize
*/
void customize(T cacheManager);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.cache.CacheManager;
/**
* Invokes the available {@link CacheManagerCustomizer} instances in the context for a
* given {@link CacheManager}.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
public class CacheManagerCustomizers {
private final List<CacheManagerCustomizer<?>> customizers;
public CacheManagerCustomizers(List<? extends CacheManagerCustomizer<?>> customizers) {
this.customizers = (customizers != null) ? new ArrayList<>(customizers) : Collections.emptyList();
}
/**
* Customize the specified {@link CacheManager}. Locates all
* {@link CacheManagerCustomizer} beans able to handle the specified instance and
* invoke {@link CacheManagerCustomizer#customize(CacheManager)} on them.
* @param <T> the type of cache manager
* @param cacheManager the cache manager to customize
* @return the cache manager
*/
@SuppressWarnings("unchecked")
public <T extends CacheManager> T customize(T cacheManager) {
LambdaSafe.callbacks(CacheManagerCustomizer.class, this.customizers, cacheManager)
.withLogger(CacheManagerCustomizers.class)
.invoke((customizer) -> customizer.customize(cacheManager));
return cacheManager;
}
}

View File

@@ -0,0 +1,282 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.cache.CacheType;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* Configuration properties for the cache abstraction.
*
* @author Stephane Nicoll
* @author Eddú Meléndez
* @author Ryon Day
* @since 4.0.0
*/
@ConfigurationProperties("spring.cache")
public class CacheProperties {
/**
* Cache type. By default, auto-detected according to the environment.
*/
private CacheType type;
/**
* List of cache names to create if supported by the underlying cache manager.
* Usually, this disables the ability to create additional caches on-the-fly.
*/
private List<String> cacheNames = new ArrayList<>();
private final Caffeine caffeine = new Caffeine();
private final Couchbase couchbase = new Couchbase();
private final Infinispan infinispan = new Infinispan();
private final JCache jcache = new JCache();
private final Redis redis = new Redis();
public CacheType getType() {
return this.type;
}
public void setType(CacheType mode) {
this.type = mode;
}
public List<String> getCacheNames() {
return this.cacheNames;
}
public void setCacheNames(List<String> cacheNames) {
this.cacheNames = cacheNames;
}
public Caffeine getCaffeine() {
return this.caffeine;
}
public Couchbase getCouchbase() {
return this.couchbase;
}
public Infinispan getInfinispan() {
return this.infinispan;
}
public JCache getJcache() {
return this.jcache;
}
public Redis getRedis() {
return this.redis;
}
/**
* Resolve the config location if set.
* @param config the config resource
* @return the location or {@code null} if it is not set
* @throws IllegalArgumentException if the config attribute is set to an unknown
* location
*/
public Resource resolveConfigLocation(Resource config) {
if (config != null) {
Assert.isTrue(config.exists(),
() -> "'config' resource [%s] must exist".formatted(config.getDescription()));
return config;
}
return null;
}
/**
* Caffeine specific cache properties.
*/
public static class Caffeine {
/**
* The spec to use to create caches. See 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;
}
}
/**
* Couchbase specific cache properties.
*/
public static class Couchbase {
/**
* Entry expiration. By default the entries never expire. Note that this value is
* ultimately converted to seconds.
*/
private Duration expiration;
public Duration getExpiration() {
return this.expiration;
}
public void setExpiration(Duration expiration) {
this.expiration = expiration;
}
}
/**
* Infinispan specific cache properties.
*/
public static class Infinispan {
/**
* The location of the configuration file to use to initialize Infinispan.
*/
private Resource config;
public Resource getConfig() {
return this.config;
}
public void setConfig(Resource config) {
this.config = config;
}
}
/**
* JCache (JSR-107) specific cache properties.
*/
public static class JCache {
/**
* The location of the configuration file to use to initialize the cache manager.
* The configuration file is dependent of the underlying cache implementation.
*/
private Resource config;
/**
* Fully qualified name of the CachingProvider implementation to use to retrieve
* the JSR-107 compliant cache manager. Needed only if more than one JSR-107
* implementation is available on the classpath.
*/
private String provider;
public String getProvider() {
return this.provider;
}
public void setProvider(String provider) {
this.provider = provider;
}
public Resource getConfig() {
return this.config;
}
public void setConfig(Resource config) {
this.config = config;
}
}
/**
* Redis-specific cache properties.
*/
public static class Redis {
/**
* Entry expiration. By default the entries never expire.
*/
private Duration timeToLive;
/**
* Allow caching null values.
*/
private boolean cacheNullValues = true;
/**
* Key prefix.
*/
private String keyPrefix;
/**
* Whether to use the key prefix when writing to Redis.
*/
private boolean useKeyPrefix = true;
/**
* Whether to enable cache statistics.
*/
private boolean enableStatistics;
public Duration getTimeToLive() {
return this.timeToLive;
}
public void setTimeToLive(Duration timeToLive) {
this.timeToLive = timeToLive;
}
public boolean isCacheNullValues() {
return this.cacheNullValues;
}
public void setCacheNullValues(boolean cacheNullValues) {
this.cacheNullValues = cacheNullValues;
}
public String getKeyPrefix() {
return this.keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
public boolean isUseKeyPrefix() {
return this.useKeyPrefix;
}
public void setUseKeyPrefix(boolean useKeyPrefix) {
this.useKeyPrefix = useKeyPrefix;
}
public boolean isEnableStatistics() {
return this.enableStatistics;
}
public void setEnableStatistics(boolean enableStatistics) {
this.enableStatistics = enableStatistics;
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
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.ObjectProvider;
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
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class })
@ConditionalOnMissingBean(CacheManager.class)
@Conditional({ CacheCondition.class })
class CaffeineCacheConfiguration {
@Bean
CaffeineCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers,
ObjectProvider<Caffeine<Object, Object>> caffeine, ObjectProvider<CaffeineSpec> caffeineSpec,
ObjectProvider<CacheLoader<Object, Object>> cacheLoader) {
CaffeineCacheManager cacheManager = createCacheManager(cacheProperties, caffeine, caffeineSpec, cacheLoader);
List<String> cacheNames = cacheProperties.getCacheNames();
if (!CollectionUtils.isEmpty(cacheNames)) {
cacheManager.setCacheNames(cacheNames);
}
return customizers.customize(cacheManager);
}
private CaffeineCacheManager createCacheManager(CacheProperties cacheProperties,
ObjectProvider<Caffeine<Object, Object>> caffeine, ObjectProvider<CaffeineSpec> caffeineSpec,
ObjectProvider<CacheLoader<Object, Object>> cacheLoader) {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
setCacheBuilder(cacheProperties, caffeineSpec.getIfAvailable(), caffeine.getIfAvailable(), cacheManager);
cacheLoader.ifAvailable(cacheManager::setCacheLoader);
return cacheManager;
}
private void setCacheBuilder(CacheProperties cacheProperties, CaffeineSpec caffeineSpec,
Caffeine<Object, Object> caffeine, CaffeineCacheManager cacheManager) {
String specification = cacheProperties.getCaffeine().getSpec();
if (StringUtils.hasText(specification)) {
cacheManager.setCacheSpecification(specification);
}
else if (caffeineSpec != null) {
cacheManager.setCaffeineSpec(caffeineSpec);
}
else if (caffeine != null) {
cacheManager.setCaffeine(caffeine);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.LinkedHashSet;
import java.util.List;
import com.couchbase.client.java.Cluster;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.cache.autoconfigure.CacheProperties.Couchbase;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager.CouchbaseCacheManagerBuilder;
import org.springframework.util.ObjectUtils;
/**
* Couchbase cache configuration.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Cluster.class, CouchbaseClientFactory.class, CouchbaseCacheManager.class })
@ConditionalOnMissingBean(CacheManager.class)
@ConditionalOnSingleCandidate(CouchbaseClientFactory.class)
@Conditional(CacheCondition.class)
class CouchbaseCacheConfiguration {
@Bean
CouchbaseCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers,
ObjectProvider<CouchbaseCacheManagerBuilderCustomizer> couchbaseCacheManagerBuilderCustomizers,
CouchbaseClientFactory clientFactory) {
List<String> cacheNames = cacheProperties.getCacheNames();
CouchbaseCacheManagerBuilder builder = CouchbaseCacheManager.builder(clientFactory);
Couchbase couchbase = cacheProperties.getCouchbase();
org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration config = org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration
.defaultCacheConfig();
if (couchbase.getExpiration() != null) {
config = config.entryExpiry(couchbase.getExpiration());
}
builder.cacheDefaults(config);
if (!ObjectUtils.isEmpty(cacheNames)) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
couchbaseCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
CouchbaseCacheManager cacheManager = builder.build();
return customizers.customize(cacheManager);
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager.CouchbaseCacheManagerBuilder;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link CouchbaseCacheManagerBuilder} before it is used to build the auto-configured
* {@link CouchbaseCacheManager}.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
@FunctionalInterface
public interface CouchbaseCacheManagerBuilderCustomizer {
/**
* Customize the {@link CouchbaseCacheManagerBuilder}.
* @param builder the builder to customize
*/
void customize(CouchbaseCacheManagerBuilder builder);
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.Collection;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* Generic cache configuration based on arbitrary {@link Cache} instances defined in the
* context.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(Cache.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class GenericCacheConfiguration {
@Bean
SimpleCacheManager cacheManager(CacheManagerCustomizers customizers, Collection<Cache> caches) {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(caches);
return customizers.customize(cacheManager);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.spring.cache.HazelcastCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.hazelcast.autoconfigure.HazelcastAutoConfiguration;
import org.springframework.boot.hazelcast.autoconfigure.HazelcastConfigResourceCondition;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* Hazelcast cache configuration. Can either reuse the {@link HazelcastInstance} that has
* been configured by the general {@link HazelcastAutoConfiguration} or create a separate
* one if the {@code spring.cache.hazelcast.config} property has been set.
* <p>
* If the {@link HazelcastAutoConfiguration} has been disabled, an attempt to configure a
* default {@link HazelcastInstance} is still made, using the same defaults.
*
* @author Stephane Nicoll
* @see HazelcastConfigResourceCondition
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HazelcastInstance.class, HazelcastCacheManager.class })
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
@ConditionalOnSingleCandidate(HazelcastInstance.class)
class HazelcastCacheConfiguration {
@Bean
HazelcastCacheManager cacheManager(CacheManagerCustomizers customizers,
HazelcastInstance existingHazelcastInstance) {
HazelcastCacheManager cacheManager = new HazelcastCacheManager(existingHazelcastInstance);
return customizers.customize(cacheManager);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.io.IOException;
import java.net.URI;
import java.util.Properties;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
/**
* JCache customization for Hazelcast.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HazelcastInstance.class)
class HazelcastJCacheCustomizationConfiguration {
@Bean
HazelcastPropertiesCustomizer hazelcastPropertiesCustomizer(ObjectProvider<HazelcastInstance> hazelcastInstance,
CacheProperties cacheProperties) {
return new HazelcastPropertiesCustomizer(hazelcastInstance.getIfUnique(), cacheProperties);
}
static class HazelcastPropertiesCustomizer implements JCachePropertiesCustomizer {
private final HazelcastInstance hazelcastInstance;
private final CacheProperties cacheProperties;
HazelcastPropertiesCustomizer(HazelcastInstance hazelcastInstance, CacheProperties cacheProperties) {
this.hazelcastInstance = hazelcastInstance;
this.cacheProperties = cacheProperties;
}
@Override
public void customize(Properties properties) {
Resource configLocation = this.cacheProperties
.resolveConfigLocation(this.cacheProperties.getJcache().getConfig());
if (configLocation != null) {
// Hazelcast does not use the URI as a mean to specify a custom config.
properties.setProperty("hazelcast.config.location", toUri(configLocation).toString());
}
else if (this.hazelcastInstance != null) {
properties.put("hazelcast.instance.itself", this.hazelcastInstance);
}
}
private static URI toUri(Resource config) {
try {
return config.getURI();
}
catch (IOException ex) {
throw new IllegalArgumentException("Could not get URI from " + config, ex);
}
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils;
/**
* Infinispan cache configuration.
*
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Raja Kolli
* @since 4.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SpringEmbeddedCacheManager.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
public class InfinispanCacheConfiguration {
@Bean
public SpringEmbeddedCacheManager cacheManager(CacheManagerCustomizers customizers,
EmbeddedCacheManager embeddedCacheManager) {
SpringEmbeddedCacheManager cacheManager = new SpringEmbeddedCacheManager(embeddedCacheManager);
return customizers.customize(cacheManager);
}
@Bean(destroyMethod = "stop")
@ConditionalOnMissingBean
public EmbeddedCacheManager infinispanCacheManager(CacheProperties cacheProperties,
ObjectProvider<ConfigurationBuilder> defaultConfigurationBuilder) throws IOException {
EmbeddedCacheManager cacheManager = createEmbeddedCacheManager(cacheProperties);
List<String> cacheNames = cacheProperties.getCacheNames();
if (!CollectionUtils.isEmpty(cacheNames)) {
cacheNames.forEach((cacheName) -> cacheManager.defineConfiguration(cacheName,
getDefaultCacheConfiguration(defaultConfigurationBuilder.getIfAvailable())));
}
return cacheManager;
}
private EmbeddedCacheManager createEmbeddedCacheManager(CacheProperties cacheProperties) throws IOException {
Resource location = cacheProperties.resolveConfigLocation(cacheProperties.getInfinispan().getConfig());
if (location != null) {
try (InputStream in = location.getInputStream()) {
return new DefaultCacheManager(in);
}
}
return new DefaultCacheManager();
}
private org.infinispan.configuration.cache.Configuration getDefaultCacheConfiguration(
ConfigurationBuilder defaultConfigurationBuilder) {
if (defaultConfigurationBuilder != null) {
return defaultConfigurationBuilder.build();
}
return new ConfigurationBuilder().build();
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.spi.CachingProvider;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Cache configuration for JSR-107 compliant providers.
*
* @author Stephane Nicoll
* @author Madhura Bhave
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Caching.class, JCacheCacheManager.class })
@ConditionalOnMissingBean(org.springframework.cache.CacheManager.class)
@Conditional({ CacheCondition.class, JCacheCacheConfiguration.JCacheAvailableCondition.class })
@Import(HazelcastJCacheCustomizationConfiguration.class)
class JCacheCacheConfiguration implements BeanClassLoaderAware {
private ClassLoader beanClassLoader;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@Bean
JCacheCacheManager cacheManager(CacheManagerCustomizers customizers, CacheManager jCacheCacheManager) {
JCacheCacheManager cacheManager = new JCacheCacheManager(jCacheCacheManager);
return customizers.customize(cacheManager);
}
@Bean
@ConditionalOnMissingBean
CacheManager jCacheCacheManager(CacheProperties cacheProperties,
ObjectProvider<javax.cache.configuration.Configuration<?, ?>> defaultCacheConfiguration,
ObjectProvider<JCacheManagerCustomizer> cacheManagerCustomizers,
ObjectProvider<JCachePropertiesCustomizer> cachePropertiesCustomizers) throws IOException {
CacheManager jCacheCacheManager = createCacheManager(cacheProperties, cachePropertiesCustomizers);
List<String> cacheNames = cacheProperties.getCacheNames();
if (!CollectionUtils.isEmpty(cacheNames)) {
for (String cacheName : cacheNames) {
jCacheCacheManager.createCache(cacheName,
defaultCacheConfiguration.getIfAvailable(MutableConfiguration::new));
}
}
cacheManagerCustomizers.orderedStream().forEach((customizer) -> customizer.customize(jCacheCacheManager));
return jCacheCacheManager;
}
private CacheManager createCacheManager(CacheProperties cacheProperties,
ObjectProvider<JCachePropertiesCustomizer> cachePropertiesCustomizers) throws IOException {
CachingProvider cachingProvider = getCachingProvider(cacheProperties.getJcache().getProvider());
Properties properties = createCacheManagerProperties(cachePropertiesCustomizers);
Resource configLocation = cacheProperties.resolveConfigLocation(cacheProperties.getJcache().getConfig());
if (configLocation != null) {
return cachingProvider.getCacheManager(configLocation.getURI(), this.beanClassLoader, properties);
}
return cachingProvider.getCacheManager(null, this.beanClassLoader, properties);
}
private CachingProvider getCachingProvider(String cachingProviderFqn) {
if (StringUtils.hasText(cachingProviderFqn)) {
return Caching.getCachingProvider(cachingProviderFqn);
}
return Caching.getCachingProvider();
}
private Properties createCacheManagerProperties(
ObjectProvider<JCachePropertiesCustomizer> cachePropertiesCustomizers) {
Properties properties = new Properties();
cachePropertiesCustomizers.orderedStream().forEach((customizer) -> customizer.customize(properties));
return properties;
}
/**
* Determine if JCache is available. This either kicks in if a provider is available
* as defined per {@link JCacheProviderAvailableCondition} or if a
* {@link CacheManager} has already been defined.
*/
@Order(Ordered.LOWEST_PRECEDENCE)
static class JCacheAvailableCondition extends AnyNestedCondition {
JCacheAvailableCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@Conditional(JCacheProviderAvailableCondition.class)
static class JCacheProvider {
}
@ConditionalOnSingleCandidate(CacheManager.class)
static class CustomJCacheCacheManager {
}
}
/**
* Determine if a JCache provider is available. This either kicks in if a default
* {@link CachingProvider} has been found or if the property referring to the provider
* to use has been set.
*/
@Order(Ordered.LOWEST_PRECEDENCE)
static class JCacheProviderAvailableCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("JCache");
String providerProperty = "spring.cache.jcache.provider";
if (context.getEnvironment().containsProperty(providerProperty)) {
return ConditionOutcome.match(message.because("JCache provider specified"));
}
Iterator<CachingProvider> providers = Caching.getCachingProviders().iterator();
if (!providers.hasNext()) {
return ConditionOutcome.noMatch(message.didNotFind("JSR-107 provider").atAll());
}
providers.next();
if (providers.hasNext()) {
return ConditionOutcome.noMatch(message.foundExactly("multiple JSR-107 providers"));
}
return ConditionOutcome.match(message.foundExactly("single JSR-107 provider"));
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import javax.cache.CacheManager;
/**
* Callback interface that can be implemented by beans wishing to customize the cache
* manager before it is used, in particular to create additional caches.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
@FunctionalInterface
public interface JCacheManagerCustomizer {
/**
* Customize the cache manager.
* @param cacheManager the {@code javax.cache.CacheManager} to customize
*/
void customize(CacheManager cacheManager);
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.Properties;
import javax.cache.CacheManager;
import javax.cache.spi.CachingProvider;
/**
* Callback interface that can be implemented by beans wishing to customize the properties
* used by the {@link CachingProvider} to create the {@link CacheManager}.
*
* @author Stephane Nicoll
* @since 4.0.0
* @see CachingProvider#getCacheManager(java.net.URI, ClassLoader, Properties)
*/
public interface JCachePropertiesCustomizer {
/**
* Customize the properties.
* @param properties the current properties
*/
void customize(Properties properties);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.NoOpCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* No-op cache configuration used to disable caching through configuration.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class NoOpCacheConfiguration {
@Bean
NoOpCacheManager cacheManager() {
return new NoOpCacheManager();
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.LinkedHashSet;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.cache.autoconfigure.CacheProperties.Redis;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
/**
* Redis cache configuration.
*
* @author Stephane Nicoll
* @author Mark Paluch
* @author Ryon Day
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(name = "org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration")
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
@Bean
RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(
determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
if (cacheProperties.getRedis().isEnableStatistics()) {
builder.enableStatistics();
}
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return cacheManagerCustomizers.customize(builder.build());
}
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
CacheProperties cacheProperties,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ClassLoader classLoader) {
return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
}
private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
CacheProperties cacheProperties, ClassLoader classLoader) {
Redis redisProperties = cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
.defaultCacheConfig();
config = config
.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
/**
* Callback interface that can be used to customize a {@link RedisCacheManagerBuilder}.
*
* @author Dmytro Nosan
* @since 4.0.0
*/
@FunctionalInterface
public interface RedisCacheManagerBuilderCustomizer {
/**
* Customize the {@link RedisCacheManagerBuilder}.
* @param builder the builder to customize
*/
void customize(RedisCacheManagerBuilder builder);
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* Simplest cache configuration, usually used as a fallback.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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
*
* https://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.
*/
/**
* Auto-configuration for the cache abstraction.
*/
package org.springframework.boot.cache.autoconfigure;

View File

@@ -0,0 +1,17 @@
{
"groups": [],
"properties": [],
"hints": [
{
"name": "spring.cache.jcache.provider",
"providers": [
{
"name": "class-reference",
"parameters": {
"target": "javax.cache.spi.CachingProvider"
}
}
]
}
]
}

View File

@@ -0,0 +1 @@
org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.hazelcast.spring.cache.HazelcastCacheManager;
import org.cache2k.extra.spring.SpringCache2kCacheManager;
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Base class for {@link CacheAutoConfiguration} tests.
*
* @author Andy Wilkinson
*/
abstract class AbstractCacheAutoConfigurationTests {
protected final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CacheAutoConfiguration.class));
protected <T extends CacheManager> T getCacheManager(AssertableApplicationContext loaded, Class<T> type) {
CacheManager cacheManager = loaded.getBean(CacheManager.class);
assertThat(cacheManager).as("Wrong cache manager type").isInstanceOf(type);
return type.cast(cacheManager);
}
@SuppressWarnings("rawtypes")
protected ContextConsumer<AssertableApplicationContext> verifyCustomizers(String... expectedCustomizerNames) {
return (context) -> {
CacheManager cacheManager = getCacheManager(context, CacheManager.class);
List<String> expected = new ArrayList<>(Arrays.asList(expectedCustomizerNames));
Map<String, CacheManagerTestCustomizer> customizer = context
.getBeansOfType(CacheManagerTestCustomizer.class);
customizer.forEach((key, value) -> {
if (expected.contains(key)) {
expected.remove(key);
assertThat(value.cacheManager).isSameAs(cacheManager);
}
else {
assertThat(value.cacheManager).isNull();
}
});
assertThat(expected).isEmpty();
};
}
@Configuration(proxyBeanMethods = false)
static class CacheManagerCustomizersConfiguration {
@Bean
CacheManagerCustomizer<CacheManager> allCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean
CacheManagerCustomizer<ConcurrentMapCacheManager> simpleCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean
CacheManagerCustomizer<SimpleCacheManager> genericCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean
CacheManagerCustomizer<CouchbaseCacheManager> couchbaseCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean
CacheManagerCustomizer<RedisCacheManager> redisCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean
CacheManagerCustomizer<HazelcastCacheManager> hazelcastCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean
CacheManagerCustomizer<SpringEmbeddedCacheManager> infinispanCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean
CacheManagerCustomizer<SpringCache2kCacheManager> cache2kCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean
CacheManagerCustomizer<CaffeineCacheManager> caffeineCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
}
abstract static class CacheManagerTestCustomizer<T extends CacheManager> implements CacheManagerCustomizer<T> {
T cacheManager;
@Override
public void customize(T cacheManager) {
if (this.cacheManager != null) {
throw new IllegalStateException("Customized invoked twice");
}
this.cacheManager = cacheManager;
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* @author Stephane Nicoll
*/
class CacheManagerCustomizersTests {
@Test
void customizeWithNullCustomizersShouldDoNothing() {
new CacheManagerCustomizers(null).customize(mock(CacheManager.class));
}
@Test
void customizeSimpleCacheManager() {
CacheManagerCustomizers customizers = new CacheManagerCustomizers(
Collections.singletonList(new CacheNamesCacheManagerCustomizer()));
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
customizers.customize(cacheManager);
assertThat(cacheManager.getCacheNames()).containsOnly("one", "two");
}
@Test
void customizeShouldCheckGeneric() {
List<TestCustomizer<?>> list = new ArrayList<>();
list.add(new TestCustomizer<>());
list.add(new TestConcurrentMapCacheManagerCustomizer());
CacheManagerCustomizers customizers = new CacheManagerCustomizers(list);
customizers.customize(mock(CacheManager.class));
assertThat(list.get(0).getCount()).isOne();
assertThat(list.get(1).getCount()).isZero();
customizers.customize(mock(ConcurrentMapCacheManager.class));
assertThat(list.get(0).getCount()).isEqualTo(2);
assertThat(list.get(1).getCount()).isOne();
customizers.customize(mock(CaffeineCacheManager.class));
assertThat(list.get(0).getCount()).isEqualTo(3);
assertThat(list.get(1).getCount()).isOne();
}
static class CacheNamesCacheManagerCustomizer implements CacheManagerCustomizer<ConcurrentMapCacheManager> {
@Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(Arrays.asList("one", "two"));
}
}
static class TestCustomizer<T extends CacheManager> implements CacheManagerCustomizer<T> {
private int count;
@Override
public void customize(T cacheManager) {
this.count++;
}
int getCount() {
return this.count;
}
}
static class TestConcurrentMapCacheManagerCustomizer extends TestCustomizer<ConcurrentMapCacheManager> {
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2025 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
*
* https://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.cache.autoconfigure;
import org.ehcache.jsr107.EhcacheCachingProvider;
import org.junit.jupiter.api.Test;
import org.springframework.boot.cache.autoconfigure.CacheAutoConfigurationTests.DefaultCacheConfiguration;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CacheAutoConfiguration} with EhCache 3.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
*/
@ClassPathExclusions("ehcache-2*.jar")
class EhCache3CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests {
@Test
void ehcache3AsJCacheWithCaches() {
String cachingProviderFqn = EhcacheCachingProvider.class.getName();
this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class)
.withPropertyValues("spring.cache.type=jcache", "spring.cache.jcache.provider=" + cachingProviderFqn,
"spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar")
.run((context) -> {
JCacheCacheManager cacheManager = getCacheManager(context, JCacheCacheManager.class);
assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar");
});
}
@Test
@WithResource(name = "ehcache3.xml", content = """
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
xsi:schemaLocation="
http://www.ehcache.org/v3 https://www.ehcache.org/schema/ehcache-core-3.10.xsd
http://www.ehcache.org/v3/jsr107 https://www.ehcache.org/schema/ehcache-107-ext-3.10.xsd">
<cache-template name="example">
<heap unit="entries">200</heap>
</cache-template>
<cache alias="foo" uses-template="example">
<expiry>
<ttl unit="seconds">600</ttl>
</expiry>
<jsr107:mbeans enable-statistics="true"/>
</cache>
<cache alias="bar" uses-template="example">
<expiry>
<ttl unit="seconds">400</ttl>
</expiry>
</cache>
</config>
""")
void ehcache3AsJCacheWithConfig() {
String cachingProviderFqn = EhcacheCachingProvider.class.getName();
String configLocation = "ehcache3.xml";
this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class)
.withPropertyValues("spring.cache.type=jcache", "spring.cache.jcache.provider=" + cachingProviderFqn,
"spring.cache.jcache.config=" + configLocation)
.run((context) -> {
JCacheCacheManager cacheManager = getCacheManager(context, JCacheCacheManager.class);
Resource configResource = new ClassPathResource(configLocation);
assertThat(cacheManager.getCacheManager().getURI()).isEqualTo(configResource.getURI());
assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar");
});
}
}

View File

@@ -0,0 +1,4 @@
#
# Test JSR 107 provider for testing purposes only.
#
org.springframework.boot.autoconfigure.cache.support.MockCachingProvider

View File

@@ -0,0 +1,19 @@
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-5.0.xsd"
xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<queue name="foobar"/>
<map name="foobar">
<time-to-live-seconds>3600</time-to-live-seconds>
<max-idle-seconds>600</max-idle-seconds>
</map>
<network>
<join>
<auto-detection enabled="false" />
<multicast enabled="false"/>
</join>
</network>
</hazelcast>