Browse Source
Add a `ConfigurationPropertyCaching` utility interface that can be used to control the property source caching. Prior to this commit, a `ConfigurationPropertySource` that was backed by a mutable `EnumerablePropertySource` would need to call the `getPropertyNames()` method each time a property was accessed. Since this this operation can be expensive, we now provide a way to cache the results for a specific length of time. This commit also improves the performance of immutable property sources by limiting the number of candidates that need to be searched. Previously, all mapped names would be enumerated. Now, mappings are grouped by `ConfigurationPropertyName`. This is especially helpful when the `ConfigurationPropertyName` isn't mapped at all since the hash based map lookup will be very fast and the resulting mappings will be empty. Closes gh-20625pull/21361/head
23 changed files with 1115 additions and 519 deletions
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.context.properties.source; |
||||
|
||||
/** |
||||
* Interface used to indicate that a {@link ConfigurationPropertySource} supports |
||||
* {@link ConfigurationPropertyCaching}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
interface CachingConfigurationPropertySource { |
||||
|
||||
/** |
||||
* Return {@link ConfigurationPropertyCaching} for this source. |
||||
* @return source caching |
||||
*/ |
||||
ConfigurationPropertyCaching getCaching(); |
||||
|
||||
/** |
||||
* Find {@link ConfigurationPropertyCaching} for the given source. |
||||
* @param source the configuration property source |
||||
* @return a {@link ConfigurationPropertyCaching} instance or {@code null} if the |
||||
* source does not support caching. |
||||
*/ |
||||
static ConfigurationPropertyCaching find(ConfigurationPropertySource source) { |
||||
if (source instanceof CachingConfigurationPropertySource) { |
||||
return ((CachingConfigurationPropertySource) source).getCaching(); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,110 @@
@@ -0,0 +1,110 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.context.properties.source; |
||||
|
||||
import java.time.Duration; |
||||
|
||||
import org.springframework.core.env.Environment; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Interface that can be used to control configuration property source caches. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 2.3.0 |
||||
*/ |
||||
public interface ConfigurationPropertyCaching { |
||||
|
||||
/** |
||||
* Enable caching with an unlimited time-to-live. |
||||
*/ |
||||
void enable(); |
||||
|
||||
/** |
||||
* Disable caching. |
||||
*/ |
||||
void disable(); |
||||
|
||||
/** |
||||
* Set amount of time that an item can live in the cache. Calling this method will |
||||
* also enable the cache. |
||||
* @param timeToLive the time to live value. |
||||
*/ |
||||
void setTimeToLive(Duration timeToLive); |
||||
|
||||
/** |
||||
* Clear the cache and force it to be reloaded on next access. |
||||
*/ |
||||
void clear(); |
||||
|
||||
/** |
||||
* Get for all configuration property sources in the environment. |
||||
* @param environment the spring environment |
||||
* @return a caching instance that controls all sources in the environment |
||||
*/ |
||||
static ConfigurationPropertyCaching get(Environment environment) { |
||||
return get(environment, null); |
||||
} |
||||
|
||||
/** |
||||
* Get for a specific configuration property source in the environment. |
||||
* @param environment the spring environment |
||||
* @param underlyingSource the |
||||
* {@link ConfigurationPropertySource#getUnderlyingSource() underlying source} that |
||||
* must match |
||||
* @return a caching instance that controls the matching source |
||||
*/ |
||||
static ConfigurationPropertyCaching get(Environment environment, Object underlyingSource) { |
||||
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment); |
||||
return get(sources, underlyingSource); |
||||
} |
||||
|
||||
/** |
||||
* Get for all specified configuration property sources. |
||||
* @param sources the configuration property sources |
||||
* @return a caching instance that controls the sources |
||||
*/ |
||||
static ConfigurationPropertyCaching get(Iterable<ConfigurationPropertySource> sources) { |
||||
return get(sources, null); |
||||
} |
||||
|
||||
/** |
||||
* Get for a specific configuration property source in the specified configuration |
||||
* property sources. |
||||
* @param sources the configuration property sources |
||||
* @param underlyingSource the |
||||
* {@link ConfigurationPropertySource#getUnderlyingSource() underlying source} that |
||||
* must match |
||||
* @return a caching instance that controls the matching source |
||||
*/ |
||||
static ConfigurationPropertyCaching get(Iterable<ConfigurationPropertySource> sources, Object underlyingSource) { |
||||
Assert.notNull(sources, "Sources must not be null"); |
||||
if (underlyingSource == null) { |
||||
return new ConfigurationPropertySourcesCaching(sources); |
||||
} |
||||
for (ConfigurationPropertySource source : sources) { |
||||
if (source.getUnderlyingSource() == underlyingSource) { |
||||
ConfigurationPropertyCaching caching = CachingConfigurationPropertySource.find(source); |
||||
if (caching != null) { |
||||
return caching; |
||||
} |
||||
} |
||||
} |
||||
throw new IllegalStateException("Unable to find cache from configuration property sources"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.context.properties.source; |
||||
|
||||
import java.time.Duration; |
||||
import java.util.function.Consumer; |
||||
|
||||
/** |
||||
* {@link ConfigurationPropertyCaching} for an {@link Iterable iterable} set of |
||||
* {@link ConfigurationPropertySource} instances. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class ConfigurationPropertySourcesCaching implements ConfigurationPropertyCaching { |
||||
|
||||
private final Iterable<ConfigurationPropertySource> sources; |
||||
|
||||
ConfigurationPropertySourcesCaching(Iterable<ConfigurationPropertySource> sources) { |
||||
this.sources = sources; |
||||
} |
||||
|
||||
@Override |
||||
public void enable() { |
||||
forEach(ConfigurationPropertyCaching::enable); |
||||
} |
||||
|
||||
@Override |
||||
public void disable() { |
||||
forEach(ConfigurationPropertyCaching::disable); |
||||
} |
||||
|
||||
@Override |
||||
public void setTimeToLive(Duration timeToLive) { |
||||
forEach((caching) -> caching.setTimeToLive(timeToLive)); |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
forEach(ConfigurationPropertyCaching::clear); |
||||
} |
||||
|
||||
private void forEach(Consumer<ConfigurationPropertyCaching> action) { |
||||
if (this.sources != null) { |
||||
for (ConfigurationPropertySource source : this.sources) { |
||||
ConfigurationPropertyCaching caching = CachingConfigurationPropertySource.find(source); |
||||
if (caching != null) { |
||||
action.accept(caching); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,73 +0,0 @@
@@ -1,73 +0,0 @@
|
||||
/* |
||||
* 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.boot.context.properties.source; |
||||
|
||||
import org.springframework.core.env.PropertySource; |
||||
|
||||
/** |
||||
* Details a mapping between a {@link PropertySource} item and a |
||||
* {@link ConfigurationPropertySource} item. |
||||
* |
||||
* @author Phillip Webb |
||||
* @author Madhura Bhave |
||||
* @see SpringConfigurationPropertySource |
||||
*/ |
||||
class PropertyMapping { |
||||
|
||||
private final String propertySourceName; |
||||
|
||||
private final ConfigurationPropertyName configurationPropertyName; |
||||
|
||||
/** |
||||
* Create a new {@link PropertyMapper} instance. |
||||
* @param propertySourceName the {@link PropertySource} name |
||||
* @param configurationPropertyName the {@link ConfigurationPropertySource} |
||||
* {@link ConfigurationPropertyName} |
||||
*/ |
||||
PropertyMapping(String propertySourceName, ConfigurationPropertyName configurationPropertyName) { |
||||
this.propertySourceName = propertySourceName; |
||||
this.configurationPropertyName = configurationPropertyName; |
||||
} |
||||
|
||||
/** |
||||
* Return the mapped {@link PropertySource} name. |
||||
* @return the property source name (never {@code null}) |
||||
*/ |
||||
String getPropertySourceName() { |
||||
return this.propertySourceName; |
||||
} |
||||
|
||||
/** |
||||
* Return the mapped {@link ConfigurationPropertySource} |
||||
* {@link ConfigurationPropertyName}. |
||||
* @return the configuration property source name (never {@code null}) |
||||
*/ |
||||
ConfigurationPropertyName getConfigurationPropertyName() { |
||||
return this.configurationPropertyName; |
||||
} |
||||
|
||||
/** |
||||
* Return if this mapping is applicable for the given |
||||
* {@link ConfigurationPropertyName}. |
||||
* @param name the name to check |
||||
* @return if the mapping is applicable |
||||
*/ |
||||
boolean isApplicable(ConfigurationPropertyName name) { |
||||
return this.configurationPropertyName.equals(name); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,114 @@
@@ -0,0 +1,114 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.context.properties.source; |
||||
|
||||
import java.lang.ref.SoftReference; |
||||
import java.time.Duration; |
||||
import java.time.Instant; |
||||
import java.util.function.Supplier; |
||||
import java.util.function.UnaryOperator; |
||||
|
||||
/** |
||||
* Simple cache that uses a {@link SoftReference} to cache a value for as long as |
||||
* possible. |
||||
* |
||||
* @param <T> the value type |
||||
* @author Phillip Webb |
||||
*/ |
||||
class SoftReferenceConfigurationPropertyCache<T> implements ConfigurationPropertyCaching { |
||||
|
||||
private static final Duration UNLIMITED = Duration.ZERO; |
||||
|
||||
private final boolean neverExpire; |
||||
|
||||
private volatile Duration timeToLive; |
||||
|
||||
private volatile SoftReference<T> value = new SoftReference<>(null); |
||||
|
||||
private volatile Instant lastAccessed = now(); |
||||
|
||||
SoftReferenceConfigurationPropertyCache(boolean neverExpire) { |
||||
this.neverExpire = neverExpire; |
||||
} |
||||
|
||||
@Override |
||||
public void enable() { |
||||
this.timeToLive = UNLIMITED; |
||||
} |
||||
|
||||
@Override |
||||
public void disable() { |
||||
this.timeToLive = null; |
||||
} |
||||
|
||||
@Override |
||||
public void setTimeToLive(Duration timeToLive) { |
||||
this.timeToLive = (timeToLive == null || timeToLive.isZero()) ? null : timeToLive; |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
this.lastAccessed = null; |
||||
} |
||||
|
||||
/** |
||||
* Get an value from the cache, creating it if necessary. |
||||
* @param factory a factory used to create the item if there is no reference to it. |
||||
* @param refreshAction action called to refresh the value if it has expired |
||||
* @return the value from the cache |
||||
*/ |
||||
T get(Supplier<T> factory, UnaryOperator<T> refreshAction) { |
||||
T value = getValue(); |
||||
if (value == null) { |
||||
value = refreshAction.apply(factory.get()); |
||||
setValue(value); |
||||
} |
||||
else if (hasExpired()) { |
||||
value = refreshAction.apply(value); |
||||
setValue(value); |
||||
} |
||||
if (!this.neverExpire) { |
||||
this.lastAccessed = now(); |
||||
} |
||||
return value; |
||||
} |
||||
|
||||
private boolean hasExpired() { |
||||
if (this.neverExpire) { |
||||
return false; |
||||
} |
||||
Duration timeToLive = this.timeToLive; |
||||
Instant lastAccessed = this.lastAccessed; |
||||
if (timeToLive == null || lastAccessed == null) { |
||||
return true; |
||||
} |
||||
return !UNLIMITED.equals(timeToLive) && now().isAfter(lastAccessed.plus(timeToLive)); |
||||
} |
||||
|
||||
protected Instant now() { |
||||
return Instant.now(); |
||||
} |
||||
|
||||
protected T getValue() { |
||||
return this.value.get(); |
||||
} |
||||
|
||||
protected void setValue(T value) { |
||||
this.value = new SoftReference<>(value); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.context.properties.source; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.withSettings; |
||||
|
||||
/** |
||||
* Tests for {@link CachingConfigurationPropertySource}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class CachingConfigurationPropertySourceTests { |
||||
|
||||
@Test |
||||
void findWhenNullSourceReturnsNull() { |
||||
ConfigurationPropertySource source = null; |
||||
assertThat(CachingConfigurationPropertySource.find(source)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void findWhenNotCachingConfigurationPropertySourceReturnsNull() { |
||||
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class); |
||||
assertThat(CachingConfigurationPropertySource.find(source)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void findWhenCachingConfigurationPropertySourceReturnsCaching() { |
||||
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class, |
||||
withSettings().extraInterfaces(CachingConfigurationPropertySource.class)); |
||||
ConfigurationPropertyCaching caching = mock(ConfigurationPropertyCaching.class); |
||||
given(((CachingConfigurationPropertySource) source).getCaching()).willReturn(caching); |
||||
assertThat(CachingConfigurationPropertySource.find(source)).isEqualTo(caching); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.context.properties.source; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.core.env.MapPropertySource; |
||||
import org.springframework.core.env.StandardEnvironment; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
/** |
||||
* Tests for {@link ConfigurationPropertyCaching}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class ConfigurationPropertyCachingTests { |
||||
|
||||
private StandardEnvironment environment; |
||||
|
||||
private MapPropertySource propertySource; |
||||
|
||||
@BeforeEach |
||||
void setup() { |
||||
this.environment = new StandardEnvironment(); |
||||
this.propertySource = new MapPropertySource("test", Collections.singletonMap("spring", "boot")); |
||||
this.environment.getPropertySources().addLast(this.propertySource); |
||||
} |
||||
|
||||
@Test |
||||
void getFromEnvironmentReturnsCaching() { |
||||
ConfigurationPropertyCaching caching = ConfigurationPropertyCaching.get(this.environment); |
||||
assertThat(caching).isInstanceOf(ConfigurationPropertySourcesCaching.class); |
||||
} |
||||
|
||||
@Test |
||||
void getFromEnvironmentForUnderlyingSourceReturnsCaching() { |
||||
ConfigurationPropertyCaching caching = ConfigurationPropertyCaching.get(this.environment, this.propertySource); |
||||
assertThat(caching).isInstanceOf(SoftReferenceConfigurationPropertyCache.class); |
||||
} |
||||
|
||||
@Test |
||||
void getFromSourcesWhenSourcesIsNullThrowsException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> ConfigurationPropertyCaching.get((Iterable<ConfigurationPropertySource>) null)) |
||||
.withMessage("Sources must not be null"); |
||||
} |
||||
|
||||
@Test |
||||
void getFromSourcesReturnsCachingComposite() { |
||||
List<ConfigurationPropertySource> sources = new ArrayList<>(); |
||||
sources.add(SpringConfigurationPropertySource.from(this.propertySource)); |
||||
ConfigurationPropertyCaching caching = ConfigurationPropertyCaching.get(sources); |
||||
assertThat(caching).isInstanceOf(ConfigurationPropertySourcesCaching.class); |
||||
} |
||||
|
||||
@Test |
||||
void getFromSourcesForUnderlyingSourceReturnsCaching() { |
||||
List<ConfigurationPropertySource> sources = new ArrayList<>(); |
||||
sources.add(SpringConfigurationPropertySource.from(this.propertySource)); |
||||
ConfigurationPropertyCaching caching = ConfigurationPropertyCaching.get(sources, this.propertySource); |
||||
assertThat(caching).isInstanceOf(SoftReferenceConfigurationPropertyCache.class); |
||||
} |
||||
|
||||
@Test |
||||
void getFromSourcesForUnderlyingSourceWhenCantFindThrowsException() { |
||||
List<ConfigurationPropertySource> sources = new ArrayList<>(); |
||||
sources.add(SpringConfigurationPropertySource.from(this.propertySource)); |
||||
MapPropertySource anotherPropertySource = new MapPropertySource("test2", Collections.emptyMap()); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> ConfigurationPropertyCaching.get(sources, anotherPropertySource)) |
||||
.withMessage("Unable to find cache from configuration property sources"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.context.properties.source; |
||||
|
||||
import java.time.Duration; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.withSettings; |
||||
|
||||
/** |
||||
* Tests for {@link ConfigurationPropertySourcesCaching}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class ConfigurationPropertySourcesCachingTests { |
||||
|
||||
private List<ConfigurationPropertySource> sources; |
||||
|
||||
private ConfigurationPropertySourcesCaching caching; |
||||
|
||||
@BeforeEach |
||||
void setup() { |
||||
this.sources = new ArrayList<>(); |
||||
for (int i = 0; i < 4; i++) { |
||||
this.sources.add(mockSource(i % 2 == 0)); |
||||
} |
||||
this.caching = new ConfigurationPropertySourcesCaching(this.sources); |
||||
} |
||||
|
||||
private ConfigurationPropertySource mockSource(boolean cachingSource) { |
||||
if (!cachingSource) { |
||||
return mock(ConfigurationPropertySource.class); |
||||
} |
||||
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class, |
||||
withSettings().extraInterfaces(CachingConfigurationPropertySource.class)); |
||||
ConfigurationPropertyCaching caching = mock(ConfigurationPropertyCaching.class); |
||||
given(((CachingConfigurationPropertySource) source).getCaching()).willReturn(caching); |
||||
return source; |
||||
} |
||||
|
||||
@Test |
||||
void enableDelegatesToCachingConfigurationPropertySources() { |
||||
this.caching.enable(); |
||||
verify(getCaching(0)).enable(); |
||||
verify(getCaching(2)).enable(); |
||||
} |
||||
|
||||
@Test |
||||
void enableWhenSourcesIsNullDoesNothing() { |
||||
new ConfigurationPropertySourcesCaching(null).enable(); |
||||
} |
||||
|
||||
@Test |
||||
void disableDelegatesToCachingConfigurationPropertySources() { |
||||
this.caching.disable(); |
||||
verify(getCaching(0)).disable(); |
||||
verify(getCaching(2)).disable(); |
||||
} |
||||
|
||||
@Test |
||||
void disableWhenSourcesIsNullDoesNothing() { |
||||
new ConfigurationPropertySourcesCaching(null).disable(); |
||||
} |
||||
|
||||
@Test |
||||
void setTimeToLiveDelegatesToCachingConfigurationPropertySources() { |
||||
Duration ttl = Duration.ofDays(1); |
||||
this.caching.setTimeToLive(ttl); |
||||
verify(getCaching(0)).setTimeToLive(ttl); |
||||
verify(getCaching(2)).setTimeToLive(ttl); |
||||
} |
||||
|
||||
@Test |
||||
void setTimeToLiveWhenSourcesIsNullDoesNothing() { |
||||
new ConfigurationPropertySourcesCaching(null).setTimeToLive(Duration.ofSeconds(1)); |
||||
} |
||||
|
||||
@Test |
||||
void clearDelegatesToCachingConfigurationPropertySources() { |
||||
this.caching.clear(); |
||||
verify(getCaching(0)).clear(); |
||||
verify(getCaching(2)).clear(); |
||||
} |
||||
|
||||
@Test |
||||
void clearWhenSourcesIsNullDoesNothing() { |
||||
new ConfigurationPropertySourcesCaching(null).enable(); |
||||
} |
||||
|
||||
private ConfigurationPropertyCaching getCaching(int index) { |
||||
return CachingConfigurationPropertySource.find(this.sources.get(index)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,169 @@
@@ -0,0 +1,169 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.context.properties.source; |
||||
|
||||
import java.time.Clock; |
||||
import java.time.Duration; |
||||
import java.time.Instant; |
||||
import java.time.ZoneOffset; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link SoftReferenceConfigurationPropertyCache}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class SoftReferenceConfigurationPropertyCacheTests { |
||||
|
||||
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2020-01-02T09:00:00Z"), ZoneOffset.UTC); |
||||
|
||||
private Clock clock = FIXED_CLOCK; |
||||
|
||||
private AtomicInteger createCount = new AtomicInteger(); |
||||
|
||||
private TestSoftReferenceConfigurationPropertyCache cache = new TestSoftReferenceConfigurationPropertyCache(false); |
||||
|
||||
@Test |
||||
void getReturnsValueWithCorrectCounts() { |
||||
get(this.cache).assertCounts(0, 0); |
||||
get(this.cache).assertCounts(0, 1); |
||||
get(this.cache).assertCounts(0, 2); |
||||
} |
||||
|
||||
@Test |
||||
void getWhenNeverExpireReturnsValueWithCorrectCounts() { |
||||
this.cache = new TestSoftReferenceConfigurationPropertyCache(true); |
||||
get(this.cache).assertCounts(0, 0); |
||||
get(this.cache).assertCounts(0, 0); |
||||
get(this.cache).assertCounts(0, 0); |
||||
} |
||||
|
||||
@Test |
||||
void enableEnablesCachingWithUnlimtedTimeToLive() { |
||||
this.cache.enable(); |
||||
get(this.cache).assertCounts(0, 0); |
||||
tick(Duration.ofDays(300)); |
||||
get(this.cache).assertCounts(0, 0); |
||||
} |
||||
|
||||
@Test |
||||
void setTimeToLiveEnablesCachingWithTimeToLive() { |
||||
this.cache.setTimeToLive(Duration.ofDays(1)); |
||||
get(this.cache).assertCounts(0, 0); |
||||
tick(Duration.ofHours(2)); |
||||
get(this.cache).assertCounts(0, 0); |
||||
tick(Duration.ofDays(2)); |
||||
get(this.cache).assertCounts(0, 1); |
||||
} |
||||
|
||||
@Test |
||||
void setTimeToLiveWhenZeroDisablesCaching() { |
||||
this.cache.setTimeToLive(Duration.ZERO); |
||||
get(this.cache).assertCounts(0, 0); |
||||
get(this.cache).assertCounts(0, 1); |
||||
get(this.cache).assertCounts(0, 2); |
||||
} |
||||
|
||||
@Test |
||||
void setTimeToLiveWhenNullDisablesCaching() { |
||||
this.cache.setTimeToLive(null); |
||||
get(this.cache).assertCounts(0, 0); |
||||
get(this.cache).assertCounts(0, 1); |
||||
get(this.cache).assertCounts(0, 2); |
||||
} |
||||
|
||||
@Test |
||||
void clearExpiresCache() { |
||||
this.cache.enable(); |
||||
get(this.cache).assertCounts(0, 0); |
||||
get(this.cache).assertCounts(0, 0); |
||||
this.cache.clear(); |
||||
get(this.cache).assertCounts(0, 1); |
||||
|
||||
} |
||||
|
||||
private Value get(SoftReferenceConfigurationPropertyCache<Value> cache) { |
||||
return cache.get(this::createValue, this::updateValue); |
||||
} |
||||
|
||||
private Value createValue() { |
||||
return new Value(this.createCount.getAndIncrement(), -1); |
||||
} |
||||
|
||||
private Value updateValue(Value value) { |
||||
return new Value(value.createCount, value.refreshCount + 1); |
||||
} |
||||
|
||||
private void tick(Duration duration) { |
||||
this.clock = Clock.offset(this.clock, duration); |
||||
} |
||||
|
||||
/** |
||||
* Testable {@link SoftReferenceConfigurationPropertyCache} that actually uses real |
||||
* references. |
||||
*/ |
||||
class TestSoftReferenceConfigurationPropertyCache extends SoftReferenceConfigurationPropertyCache<Value> { |
||||
|
||||
private Value value; |
||||
|
||||
TestSoftReferenceConfigurationPropertyCache(boolean neverExpire) { |
||||
super(neverExpire); |
||||
} |
||||
|
||||
@Override |
||||
protected Value getValue() { |
||||
return this.value; |
||||
} |
||||
|
||||
@Override |
||||
protected void setValue(Value value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
@Override |
||||
protected Instant now() { |
||||
return SoftReferenceConfigurationPropertyCacheTests.this.clock.instant(); |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Value used for testing. |
||||
*/ |
||||
static class Value { |
||||
|
||||
private final int createCount; |
||||
|
||||
private int refreshCount; |
||||
|
||||
Value(int createCount, int refreshCount) { |
||||
this.createCount = createCount; |
||||
this.refreshCount = refreshCount; |
||||
} |
||||
|
||||
void assertCounts(int expectedCreateCount, int expectedRefreshCount) { |
||||
assertThat(this.createCount).as("created").isEqualTo(expectedCreateCount); |
||||
assertThat(this.refreshCount).as("refreshed").isEqualTo(expectedRefreshCount); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue