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:
committed by
GitHub
parent
05d936803f
commit
da0e06eb7a
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user