Add evictor based loadbalancer caching (#644)

* Add Evictor dependency. Implement EvictorCache.

* Add EvictorBasedLoadBalancerCacheManager. Add evictor to starter.

* Set caffeine InitialCapacity from properties.

* Add tests. Add javadocs. Fix creating multiple cache instances.

* Reformat. Change property name. Add documentation.

* Add more tests.

* Move evictor version to parent. Add more tests.

* Ignore .flattened-pom.xml.

* Encapsulate Evictor and switch to non-evictor specific class naming.

* Adjust the docs.

* Fix after code review.
This commit is contained in:
Olga Maciaszek-Sharma
2019-11-25 20:34:19 +01:00
committed by GitHub
parent 05d936803f
commit da0e06eb7a
24 changed files with 599 additions and 1393 deletions

View File

@@ -29,6 +29,9 @@ import static org.springframework.cloud.loadbalancer.core.CachingServiceInstance
*
* @author Olga Maciaszek-Sharma
* @since 2.2.0
* @see <a href="https://github.com/ben-manes/caffeine>Caffeine</a>
* @see CaffeineCacheManager
* @see Caffeine
*/
public class CaffeineBasedLoadBalancerCacheManager extends CaffeineCacheManager
implements LoadBalancerCacheManager {
@@ -40,8 +43,8 @@ public class CaffeineBasedLoadBalancerCacheManager extends CaffeineCacheManager
setCacheSpecification(properties.getCaffeine().getSpec());
}
else {
setCaffeine(Caffeine.newBuilder().expireAfterWrite(properties.getTtl())
.softValues());
setCaffeine(Caffeine.newBuilder().initialCapacity(properties.getCapacity())
.expireAfterWrite(properties.getTtl()).softValues());
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2012-2019 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.cloud.loadbalancer.cache;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import javax.validation.constraints.Null;
import com.stoyanr.evictor.ConcurrentMapWithTimedEviction;
import com.stoyanr.evictor.map.ConcurrentHashMapWithTimedEviction;
import com.stoyanr.evictor.scheduler.DelayedTaskEvictionScheduler;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A default {@link Cache} implementation used by Spring Cloud LoadBalancer. The current
* implementation uses {@link ConcurrentMapWithTimedEviction} underneath. Based on
* {@link ConcurrentMapCache}.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.0
* @see <a href="https://github.com/stoyanr/Evictor">Evictor</a>
* @see ConcurrentMapWithTimedEviction
* @see ConcurrentMapCache
*/
public class DefaultLoadBalancerCache extends AbstractValueAdaptingCache {
private final String name;
private final ConcurrentMapWithTimedEviction<Object, Object> cache;
private final long evictMs;
DefaultLoadBalancerCache(String name,
ConcurrentMapWithTimedEviction<Object, Object> cache, long evictMs,
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;
this.evictMs = evictMs;
}
/**
* Create a new DefaultCache with the specified name.
* @param name the name of the cache
*/
public DefaultLoadBalancerCache(String name) {
this(name, new ConcurrentHashMapWithTimedEviction<>(256,
new DelayedTaskEvictionScheduler<>()), 0, true);
}
/**
* Create a new DefaultCache with the specified name.
* @param name the name of the cache
* @param evictMs default time to evict the entries
* {@link ConcurrentMapWithTimedEviction}
* @param allowNullValues whether to accept and convert {@code null} values for this
* cache
*/
public DefaultLoadBalancerCache(String name, long evictMs, boolean allowNullValues) {
this(name, new ConcurrentHashMapWithTimedEviction<>(256,
new DelayedTaskEvictionScheduler<>()), evictMs, allowNullValues);
}
/**
* Create a new EvictorCache with the specified name.
* @param name the name of the cache
* @param allowNullValues whether to accept and convert {@code null} values for this
* cache
*/
public DefaultLoadBalancerCache(String name, boolean allowNullValues) {
this(name, new ConcurrentHashMapWithTimedEviction<>(256,
new DelayedTaskEvictionScheduler<>()), 0, allowNullValues);
}
@Override
@Null
protected Object lookup(Object key) {
return cache.get(key);
}
@Override
public String getName() {
return name;
}
@Override
public ConcurrentMap<Object, Object> getNativeCache() {
return cache;
}
@SuppressWarnings("unchecked")
@Override
@Nullable
public <T> T get(Object key, Callable<T> valueLoader) {
return (T) fromStoreValue(cache.computeIfAbsent(key, k -> {
try {
return toStoreValue(valueLoader.call());
}
catch (Throwable ex) {
throw new ValueRetrievalException(key, valueLoader, ex);
}
}));
}
public void put(Object key, @Nullable Object value, long evictMs) {
cache.put(key, toStoreValue(value), evictMs);
}
@Override
@Nullable
public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
Object existing = cache.putIfAbsent(key, toStoreValue(value), evictMs);
return toValueWrapper(existing);
}
@Nullable
public ValueWrapper putIfAbsent(Object key, @Nullable Object value, long evictMs) {
Object existing = cache.putIfAbsent(key, toStoreValue(value), evictMs);
return toValueWrapper(existing);
}
@Override
public void put(Object key, @Nullable Object value) {
cache.put(key, toStoreValue(value), evictMs);
}
@Override
public void evict(Object key) {
cache.remove(key);
}
@Override
public boolean evictIfPresent(Object key) {
return (cache.remove(key) != null);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean invalidate() {
boolean notEmpty = !cache.isEmpty();
cache.clear();
return notEmpty;
}
// Visible for tests
long getEvictMs() {
return evictMs;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2012-2019 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.cloud.loadbalancer.cache;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import com.stoyanr.evictor.map.ConcurrentHashMapWithTimedEviction;
import com.stoyanr.evictor.scheduler.DelayedTaskEvictionScheduler;
import org.springframework.cache.Cache;
import org.springframework.lang.Nullable;
import static org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier.SERVICE_INSTANCE_CACHE_NAME;
/**
* An {@link DefaultLoadBalancerCache}-based {@link LoadBalancerCacheManager}
* implementation.
*
* NOTE: This is a very basic implementation as required for the LoadBalancer caching
* mechanism at the moment. The underlying implementation can be modified in future to
* allow for passing different properties per cache name.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.0
* @see <a href="https://github.com/stoyanr/Evictor">Evictor</a>
* @see ConcurrentHashMapWithTimedEviction
*/
public class DefaultLoadBalancerCacheManager implements LoadBalancerCacheManager {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
public DefaultLoadBalancerCacheManager(
LoadBalancerCacheProperties loadBalancerCacheProperties,
String... cacheNames) {
cacheMap.putAll(createCaches(cacheNames, loadBalancerCacheProperties).stream()
.collect(Collectors.toMap(DefaultLoadBalancerCache::getName,
cache -> cache)));
}
public DefaultLoadBalancerCacheManager(
LoadBalancerCacheProperties loadBalancerCacheProperties) {
this(loadBalancerCacheProperties, SERVICE_INSTANCE_CACHE_NAME);
}
private Set<DefaultLoadBalancerCache> createCaches(String[] cacheNames,
LoadBalancerCacheProperties loadBalancerCacheProperties) {
return Arrays.stream(cacheNames).distinct()
.map(name -> new DefaultLoadBalancerCache(name,
new ConcurrentHashMapWithTimedEviction<>(
loadBalancerCacheProperties.getCapacity(),
new DelayedTaskEvictionScheduler<>()),
loadBalancerCacheProperties.getTtl().toMillis(), false))
.collect(Collectors.toSet());
}
@Override
@Nullable
public Cache getCache(String name) {
return cacheMap.get(name);
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(cacheMap.keySet());
}
}

View File

@@ -22,6 +22,7 @@ import org.springframework.cache.CacheManager;
* A marker interface for Spring Cloud LoadBalancer-specific {@link CacheManager} beans.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.0
*/
public interface LoadBalancerCacheManager extends CacheManager {

View File

@@ -41,6 +41,11 @@ public class LoadBalancerCacheProperties {
*/
private Duration ttl = Duration.ofSeconds(30);
/**
* Initial cache capacity expressed as int.
*/
private int capacity = 256;
public Caffeine getCaffeine() {
return caffeine;
}
@@ -57,6 +62,14 @@ public class LoadBalancerCacheProperties {
this.ttl = ttl;
}
int getCapacity() {
return capacity;
}
void setCapacity(int capacity) {
this.capacity = capacity;
}
/**
* Caffeine-specific LoadBalancer cache properties. NOTE: Passing your own Caffeine
* specification will override any other LoadBalancerCache settings, including TTL.

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.loadbalancer.config;
import javax.annotation.PostConstruct;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.stoyanr.evictor.ConcurrentMapWithTimedEviction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -32,6 +33,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.cloud.loadbalancer.cache.CaffeineBasedLoadBalancerCacheManager;
import org.springframework.cloud.loadbalancer.cache.DefaultLoadBalancerCacheManager;
import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager;
import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheProperties;
import org.springframework.context.annotation.Bean;
@@ -39,15 +41,15 @@ import org.springframework.context.annotation.Configuration;
/**
* An AutoConfiguration that automatically enables caching when when Spring Boot, and
* Spring Framework Cache support and Caffeine classes are present and warns if Caffeine
* is not present (we are only warning about Caffeine because the other dependencies are
* in spring-cloud-starter-loadbalancer).
* Spring Framework Cache support are present. If Caffeine is present in the classpath, it
* will be used for loadbalancer caching. If not, a default cache will be used.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.0
* @see CacheManager
* @see CacheAutoConfiguration
* @see CacheAspectSupport
* @see <a href="https://github.com/ben-manes/caffeine>Caffeine</a>
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ CacheManager.class, CacheAutoConfiguration.class })
@@ -77,8 +79,8 @@ public class LoadBalancerCacheAutoConfiguration {
void logWarning() {
if (LOG.isWarnEnabled()) {
LOG.warn(
"Spring Cloud LoadBalancer is currently working without cache. To enable cache, add "
+ "com.github.ben-manes.caffeine:caffeine dependency to classpath.");
"Spring Cloud LoadBalancer is currently working with default default cache. "
+ "You can switch to using Caffeine cache, by adding it to the classpath.");
}
}
@@ -86,15 +88,29 @@ public class LoadBalancerCacheAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Caffeine.class)
protected static class LoadBalancerCacheManagerConfiguration {
protected static class CaffeineLoadBalancerCacheManagerConfiguration {
@Bean(autowireCandidate = false)
@ConditionalOnMissingBean
LoadBalancerCacheManager loadBalancerCacheManager(
LoadBalancerCacheManager caffeineLoadBalancerCacheManager(
LoadBalancerCacheProperties cacheProperties) {
return new CaffeineBasedLoadBalancerCacheManager(cacheProperties);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("com.github.benmanes.caffeine.cache.Caffeine")
@ConditionalOnClass(ConcurrentMapWithTimedEviction.class)
protected static class DefaultLoadBalancerCacheManagerConfiguration {
@Bean(autowireCandidate = false)
@ConditionalOnMissingBean
LoadBalancerCacheManager defaultLoadBalancerCacheManager(
LoadBalancerCacheProperties cacheProperties) {
return new DefaultLoadBalancerCacheManager(cacheProperties);
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2012-2019 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.cloud.loadbalancer.cache;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.cache.CacheManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier.SERVICE_INSTANCE_CACHE_NAME;
/**
* Tests for {@link DefaultLoadBalancerCacheManager}.
*
* @author Olga Maciaszek-Sharma
*/
class DefaultLoadBalancerCacheManagerTests {
@SuppressWarnings("ConstantConditions")
@Test
void shouldCreateLoadBalancerCacheFromProperties() {
LoadBalancerCacheProperties properties = new LoadBalancerCacheProperties();
properties.setTtl(Duration.ofMinutes(5));
properties.setCapacity(128);
DefaultLoadBalancerCacheManager cacheManager = new DefaultLoadBalancerCacheManager(
properties);
assertThat(cacheManager.getCacheNames()).hasSize(1);
assertThat(cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME))
.isInstanceOf(DefaultLoadBalancerCache.class);
assertThat(((DefaultLoadBalancerCache) cacheManager
.getCache(SERVICE_INSTANCE_CACHE_NAME)).getEvictMs()).isEqualTo(300000);
}
@Test
void shouldNotThrowExceptionOnDuplicateCacheName() {
LoadBalancerCacheProperties properties = new LoadBalancerCacheProperties();
assertThatCode(
() -> new DefaultLoadBalancerCacheManager(properties, "test", "test"))
.doesNotThrowAnyException();
}
@Test
void shouldOnlyCreateOneCacheWithGivenName() {
LoadBalancerCacheProperties properties = new LoadBalancerCacheProperties();
CacheManager cacheManager = new DefaultLoadBalancerCacheManager(properties,
"test", "test");
assertThat(cacheManager.getCacheNames()).hasSize(1);
assertThat(cacheManager.getCache("test"))
.isInstanceOf(DefaultLoadBalancerCache.class);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012-2019 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.cloud.loadbalancer.cache;
import com.stoyanr.evictor.map.ConcurrentHashMapWithTimedEviction;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link DefaultLoadBalancerCache}.
*
* @author Olga Maciaszek-Sharma
*/
@ExtendWith(MockitoExtension.class)
class DefaultLoadBalancerCacheTests {
@Test
void shouldAllowNullValuesByDefault() {
DefaultLoadBalancerCache cache = new DefaultLoadBalancerCache("test");
assertThatCode(() -> cache.put("testKey", null)).doesNotThrowAnyException();
}
@Test
void shouldThrowExceptionIfNullPutWithNonNullSetup() {
DefaultLoadBalancerCache cache = new DefaultLoadBalancerCache("test", false);
assertThatIllegalArgumentException().isThrownBy(() -> cache.put("testKey", null))
.withMessageContaining(
"Cache 'test' is configured to not allow null values but null was provided");
}
@Test
void shouldNotEvictEntriesByDefault() {
DefaultLoadBalancerCache cache = new DefaultLoadBalancerCache("test");
assertThat(cache.getEvictMs()).isEqualTo(0);
}
@SuppressWarnings("unchecked")
@Test
void assertThatTtlApplied() {
ConcurrentHashMapWithTimedEviction nativeCache = mock(
ConcurrentHashMapWithTimedEviction.class);
DefaultLoadBalancerCache cache = new DefaultLoadBalancerCache("test", nativeCache,
50, true);
cache.put("testKey", "testValue");
verify(nativeCache, times(1)).put("testKey", "testValue", 50);
}
}

View File

@@ -16,15 +16,19 @@
package org.springframework.cloud.loadbalancer.config;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.support.NoOpCacheManager;
import org.springframework.cloud.loadbalancer.cache.DefaultLoadBalancerCacheManager;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@@ -43,24 +47,27 @@ class LoadBalancerCacheAutoConfigurationTests {
contextRunner.run(context -> {
assertThat(context.getBeansOfType(CacheManager.class)).hasSize(1);
assertThat(((CacheManager) context.getBean("loadBalancerCacheManager"))
.getCacheNames()).hasSize(1);
assertThat(context.getBean("loadBalancerCacheManager"))
assertThat(
((CacheManager) context.getBean("caffeineLoadBalancerCacheManager"))
.getCacheNames()).hasSize(1);
assertThat(context.getBean("caffeineLoadBalancerCacheManager"))
.isInstanceOf(CaffeineCacheManager.class);
assertThat(((CacheManager) context.getBean("loadBalancerCacheManager"))
.getCacheNames()).contains("CachingServiceInstanceListSupplierCache");
assertThat(
((CacheManager) context.getBean("caffeineLoadBalancerCacheManager"))
.getCacheNames())
.contains("CachingServiceInstanceListSupplierCache");
});
}
@Test
void loadBalancerCacheShouldNotOverrideCacheTypeSetting() {
void caffeineLoadBalancerCacheShouldNotOverrideCacheTypeSetting() {
ApplicationContextRunner contextRunner = baseApplicationRunner()
.withUserConfiguration(TestConfiguration.class)
.withPropertyValues("spring.cache.type=none");
contextRunner.run(context -> {
assertThat(context.getBeansOfType(CacheManager.class)).hasSize(2);
assertThat(context.getBean("loadBalancerCacheManager"))
assertThat(context.getBean("caffeineLoadBalancerCacheManager"))
.isInstanceOf(CaffeineCacheManager.class);
assertThat(context.getBeansOfType(CacheManager.class).get("cacheManager"))
.isInstanceOf(NoOpCacheManager.class);
@@ -79,19 +86,110 @@ class LoadBalancerCacheAutoConfigurationTests {
.isInstanceOf(CaffeineCacheManager.class);
assertThat(((CacheManager) context.getBean("cacheManager")).getCacheNames())
.isEmpty();
assertThat(((CacheManager) context.getBean("loadBalancerCacheManager"))
assertThat(
((CacheManager) context.getBean("caffeineLoadBalancerCacheManager"))
.getCacheNames()).hasSize(1);
assertThat(
((CacheManager) context.getBean("caffeineLoadBalancerCacheManager"))
.getCacheNames())
.contains("CachingServiceInstanceListSupplierCache");
});
}
@Test
void shouldNotInstantiateCaffeineLoadBalancerCacheIfDisabled() {
ApplicationContextRunner contextRunner = baseApplicationRunner()
.withPropertyValues("spring.cloud.loadbalancer.cache.enabled=false")
.withUserConfiguration(TestConfiguration.class);
contextRunner.run(context -> {
assertThat(context.getBeansOfType(CacheManager.class)).hasSize(1);
assertThat(context.getBean("cacheManager"))
.isInstanceOf(CaffeineCacheManager.class);
assertThat(((CacheManager) context.getBean("cacheManager")).getCacheNames())
.isEmpty();
});
}
@Test
void shouldUseDefaultCacheIfCaffeineNotInClasspath() {
ApplicationContextRunner contextRunner = noCaffeineRunner();
contextRunner.run(context -> {
assertThat(context.getBean(
LoadBalancerCacheAutoConfiguration.LoadBalancerCaffeineWarnLogger.class))
.isNotNull();
assertThat(context.getBeansOfType(CacheManager.class)).hasSize(1);
assertThat(((CacheManager) context.getBean("defaultLoadBalancerCacheManager"))
.getCacheNames()).hasSize(1);
assertThat(((CacheManager) context.getBean("loadBalancerCacheManager"))
assertThat(context.getBean("defaultLoadBalancerCacheManager"))
.isInstanceOf(DefaultLoadBalancerCacheManager.class);
assertThat(((CacheManager) context.getBean("defaultLoadBalancerCacheManager"))
.getCacheNames()).contains("CachingServiceInstanceListSupplierCache");
});
}
@Test
void defaultLoadBalancerCacheShouldNotOverrideCacheTypeSetting() {
ApplicationContextRunner contextRunner = noCaffeineRunner()
.withUserConfiguration(TestConfiguration.class)
.withPropertyValues("spring.cache.type=none");
contextRunner.run(context -> {
assertThat(context.getBeansOfType(CacheManager.class)).hasSize(2);
assertThat(context.getBean("defaultLoadBalancerCacheManager"))
.isInstanceOf(DefaultLoadBalancerCacheManager.class);
assertThat(context.getBeansOfType(CacheManager.class).get("cacheManager"))
.isInstanceOf(NoOpCacheManager.class);
});
}
@Test
void defaultLoadBalancerCacheShouldNotOverrideExistingCacheManager() {
ApplicationContextRunner contextRunner = noCaffeineRunner()
.withUserConfiguration(TestConfiguration.class);
contextRunner.run(context -> {
assertThat(context.getBeansOfType(CacheManager.class)).hasSize(2);
assertThat(context.getBean("cacheManager"))
.isInstanceOf(ConcurrentMapCacheManager.class);
assertThat(((CacheManager) context.getBean("cacheManager")).getCacheNames())
.isEmpty();
assertThat(((CacheManager) context.getBean("defaultLoadBalancerCacheManager"))
.getCacheNames()).hasSize(1);
assertThat(((CacheManager) context.getBean("defaultLoadBalancerCacheManager"))
.getCacheNames()).contains("CachingServiceInstanceListSupplierCache");
});
}
@Test
void shouldNotInstantiateDefaultLoadBalancerCacheIfDisabled() {
ApplicationContextRunner contextRunner = noCaffeineRunner()
.withPropertyValues("spring.cloud.loadbalancer.cache.enabled=false")
.withUserConfiguration(TestConfiguration.class);
contextRunner.run(context -> {
assertThat(context.getBeansOfType(CacheManager.class)).hasSize(1);
assertThat(context.getBean("cacheManager"))
.isInstanceOf(ConcurrentMapCacheManager.class);
assertThat(((CacheManager) context.getBean("cacheManager")).getCacheNames())
.isEmpty();
});
}
private ApplicationContextRunner baseApplicationRunner() {
return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(
CacheAutoConfiguration.class, LoadBalancerCacheAutoConfiguration.class));
}
private ApplicationContextRunner noCaffeineRunner() {
return baseApplicationRunner()
.withClassLoader(new FilteredClassLoader(Caffeine.class));
}
@Configuration(proxyBeanMethods = false)
@EnableCaching
static class TestConfiguration {