Browse Source

Add a property source that produces a random value that is cached (#719)

* Add a property source that produces a random value that is cached.
pull/732/head
Ryan Baxter 5 years ago committed by GitHub
parent
commit
4119a9c138
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      docs/src/main/asciidoc/spring-cloud-commons.adoc
  2. 109
      spring-cloud-context/src/main/java/org/springframework/cloud/util/random/CachedRandomPropertySource.java
  3. 48
      spring-cloud-context/src/main/java/org/springframework/cloud/util/random/CachedRandomPropertySourceAutoConfiguration.java
  4. 3
      spring-cloud-context/src/main/resources/META-INF/spring.factories
  5. 33
      spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherIntegrationTests.java
  6. 79
      spring-cloud-context/src/test/java/org/springframework/cloud/util/random/CachedRandomPropertySourceTests.java
  7. 3
      spring-cloud-context/src/test/resources/META-INF/spring.factories
  8. 2
      spring-cloud-context/src/test/resources/application.properties

15
docs/src/main/asciidoc/spring-cloud-commons.adoc

@ -1026,6 +1026,21 @@ public class MyConfiguration { @@ -1026,6 +1026,21 @@ public class MyConfiguration {
include::spring-cloud-circuitbreaker.adoc[leveloffset=+1]
== CachedRandomPropertySource
Spring Cloud Context provides a `PropertySource` that caches random values based on a key. Outside of the caching
functionality it works the same as Spring Boot's https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java[`RandomValuePropertySource`].
This random value might be useful in the case where you want a random value that is consistent even after the Spring Application
context restarts. The property value takes the form of `cachedrandom.[yourkey].[type]` where `yourkey` is the key in the cache. The `type` value can
be any type supported by Spring Boot's `RandomValuePropertySource`.
====
[source,properties,indent=0]
----
myrandom=${cachedrandom.appname.value}
----
====
== Configuration Properties
To see the list of all Spring Cloud Commons related configuration properties please check link:appendix.html[the Appendix page].

109
spring-cloud-context/src/main/java/org/springframework/cloud/util/random/CachedRandomPropertySource.java

@ -0,0 +1,109 @@ @@ -0,0 +1,109 @@
/*
* 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.cloud.util.random;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;
/**
* @author Ryan Baxter
*/
public class CachedRandomPropertySource
extends PropertySource<RandomValuePropertySource> {
private static final String NAME = "cachedrandom";
private static final String PREFIX = NAME + ".";
private static Map<String, Map<String, Object>> cache = new ConcurrentHashMap<>();
public CachedRandomPropertySource(
RandomValuePropertySource randomValuePropertySource) {
super(NAME, randomValuePropertySource);
}
CachedRandomPropertySource(RandomValuePropertySource randomValuePropertySource,
Map<String, Map<String, Object>> cache) {
super(NAME, randomValuePropertySource);
this.cache = cache;
}
@Override
public Object getProperty(String name) {
if (!name.startsWith(PREFIX) || name.length() == PREFIX.length()) {
return null;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Generating random property for '" + name + "'");
}
// TO avoid any weirdness from the type or key including a "." we look for the
// last "." and substring everything instead of splitting on the "."
String keyAndType = name.substring(PREFIX.length());
int lastIndexOfDot = keyAndType.lastIndexOf(".");
if (lastIndexOfDot < 0) {
return null;
}
String key = keyAndType.substring(0, lastIndexOfDot);
String type = keyAndType.substring(lastIndexOfDot + 1);
if (StringUtils.hasText(key) && StringUtils.hasText(type)) {
return getRandom(type, key);
}
else {
return null;
}
}
}
private Object getRandom(String type, String key) {
Map<String, Object> randomValueCache = getCacheForKey(key);
if (logger.isDebugEnabled()) {
logger.debug("Looking in random cache for key " + key + " with type " + type);
}
return randomValueCache.computeIfAbsent(type, (theType) -> {
if (logger.isDebugEnabled()) {
logger.debug(
"No random value found in cache for key and value, generating a new value");
}
return getSource().getProperty("random." + type);
});
}
private Map<String, Object> getCacheForKey(String key) {
if (logger.isDebugEnabled()) {
logger.debug("Looking in random cache for key: " + key);
}
return cache.computeIfAbsent(key, theKey -> {
if (logger.isDebugEnabled()) {
logger.debug("No cached value found for key: " + key);
}
return new ConcurrentHashMap<>();
});
}
public static void clearCache() {
if (cache != null) {
cache.clear();
}
}
}

48
spring-cloud-context/src/main/java/org/springframework/cloud/util/random/CachedRandomPropertySourceAutoConfiguration.java

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
/*
* 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.cloud.util.random;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
/**
* @author Ryan Baxter
*/
@Configuration(proxyBeanMethods = false)
public class CachedRandomPropertySourceAutoConfiguration {
@Autowired
ConfigurableEnvironment environment;
@PostConstruct
public void initialize() {
MutablePropertySources propertySources = environment.getPropertySources();
PropertySource propertySource = propertySources
.get(RandomValuePropertySource.RANDOM_PROPERTY_SOURCE_NAME);
if (propertySource != null) {
propertySources.addLast(new CachedRandomPropertySource(
RandomValuePropertySource.class.cast(propertySource)));
}
}
}

3
spring-cloud-context/src/main/resources/META-INF/spring.factories

@ -15,4 +15,5 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\ @@ -15,4 +15,5 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration

33
spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherIntegrationTests.java

@ -37,7 +37,7 @@ import static org.assertj.core.api.BDDAssertions.then; @@ -37,7 +37,7 @@ import static org.assertj.core.api.BDDAssertions.then;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestConfiguration.class,
properties = { "spring.datasource.hikari.read-only=false" })
properties = { "spring.datasource.hikari.read-only=false", "debug=true" })
public class ContextRefresherIntegrationTests {
@Autowired
@ -81,6 +81,17 @@ public class ContextRefresherIntegrationTests { @@ -81,6 +81,17 @@ public class ContextRefresherIntegrationTests {
then(this.properties.getMessage()).isEqualTo("Hello scope!");
}
@Test
@DirtiesContext
public void testCachedRandom() {
long cachedRandomLong = properties.getCachedRandomLong();
long randomLong = properties.randomLong();
then(cachedRandomLong).isNotNull();
this.refresher.refresh();
then(randomLong).isNotEqualTo(properties.randomLong());
then(cachedRandomLong).isEqualTo(properties.cachedRandomLong);
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TestProperties.class)
@EnableAutoConfiguration
@ -96,6 +107,10 @@ public class ContextRefresherIntegrationTests { @@ -96,6 +107,10 @@ public class ContextRefresherIntegrationTests {
private int delay;
private Long cachedRandomLong;
private Long randomLong;
@ManagedAttribute
public String getMessage() {
return this.message;
@ -114,6 +129,22 @@ public class ContextRefresherIntegrationTests { @@ -114,6 +129,22 @@ public class ContextRefresherIntegrationTests {
this.delay = delay;
}
public long getCachedRandomLong() {
return cachedRandomLong;
}
public void setCachedRandomLong(long cachedRandomLong) {
this.cachedRandomLong = cachedRandomLong;
}
public long randomLong() {
return randomLong;
}
public void setRandomLong(long randomLong) {
this.randomLong = randomLong;
}
}
}

79
spring-cloud-context/src/test/java/org/springframework/cloud/util/random/CachedRandomPropertySourceTests.java

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
/*
* 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.cloud.util.random;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Ryan Baxter
*/
@RunWith(MockitoJUnitRunner.class)
@DirtiesContext
public class CachedRandomPropertySourceTests {
@Mock
RandomValuePropertySource randomValuePropertySource;
@Before
public void setup() {
when(randomValuePropertySource.getProperty(eq("random.long")))
.thenReturn(new Long(1234));
}
@Test
public void getProperty() {
Map<String, Map<String, Object>> cache = new HashMap<>();
Map<String, Object> typeCache = new HashMap<>();
typeCache.put("long", new Long(5678));
Map<String, Object> spyedTypeCache = spy(typeCache);
cache.put("foo", spyedTypeCache);
Map<String, Map<String, Object>> spyedCache = spy(cache);
CachedRandomPropertySource cachedRandomPropertySource = new CachedRandomPropertySource(
randomValuePropertySource, spyedCache);
then(cachedRandomPropertySource.getProperty("foo.app.long")).isNull();
then(cachedRandomPropertySource.getProperty("cachedrandom.app")).isNull();
then(cachedRandomPropertySource.getProperty("cachedrandom.app.long"))
.isEqualTo(new Long(1234));
then(cachedRandomPropertySource.getProperty("cachedrandom.foo.long"))
.isEqualTo(new Long(5678));
verify(spyedCache, times(1)).computeIfAbsent(eq("app"), isA(Function.class));
verify(spyedTypeCache, times(1)).computeIfAbsent(eq("long"), isA(Function.class));
}
}

3
spring-cloud-context/src/test/resources/META-INF/spring.factories

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.TestBootstrapConfiguration,\
org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration
org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration,\
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration
org.springframework.boot.env.EnvironmentPostProcessor=\

2
spring-cloud-context/src/test/resources/application.properties

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
message:Hello scope!
delay:0
cachedRandomLong: ${cachedrandom.app.long}
randomLong: ${random.long}
debug:true
#logging.level.org.springframework.web: DEBUG
#logging.level.org.springframework.context.annotation: DEBUG

Loading…
Cancel
Save