Unverified Commit 5823daa6 by 张乐 Committed by GitHub

Merge pull request #972 from nobodyiam/auto-update-value-merge

Auto Updating Spring Placeholder Values
parents 7edfb1aa 3efd4a33
......@@ -7,6 +7,8 @@ import com.ctrip.framework.apollo.spi.ConfigRegistry;
import com.ctrip.framework.apollo.spi.DefaultConfigFactory;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManager;
import com.ctrip.framework.apollo.spi.DefaultConfigRegistry;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.http.HttpUtil;
......@@ -60,6 +62,8 @@ public class DefaultInjector implements Injector {
bind(HttpUtil.class).in(Singleton.class);
bind(ConfigServiceLocator.class).in(Singleton.class);
bind(RemoteConfigLongPollService.class).in(Singleton.class);
bind(PlaceholderHelper.class).in(Singleton.class);
bind(ConfigPropertySourceFactory.class).in(Singleton.class);
}
}
}
package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
......@@ -30,5 +31,8 @@ public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class);
}
}
......@@ -2,7 +2,9 @@ package com.ctrip.framework.apollo.spring.boot;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory;
import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySource;
import com.google.common.base.Splitter;
......@@ -39,6 +41,9 @@ public class ApolloSpringApplicationRunListener implements SpringApplicationRunL
private static final Logger logger = LoggerFactory.getLogger(ApolloSpringApplicationRunListener.class);
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector
.getInstance(ConfigPropertySourceFactory.class);
public ApolloSpringApplicationRunListener(SpringApplication application, String[] args) {
//ignore
}
......@@ -74,7 +79,7 @@ public class ApolloSpringApplicationRunListener implements SpringApplicationRunL
for (String namespace : namespaceList) {
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(new ConfigPropertySource(namespace, config));
composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
environment.getPropertySources().addFirst(composite);
......
package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import java.util.Set;
import org.springframework.core.env.EnumerablePropertySource;
......@@ -14,7 +15,7 @@ import com.ctrip.framework.apollo.Config;
public class ConfigPropertySource extends EnumerablePropertySource<Config> {
private static final String[] EMPTY_ARRAY = new String[0];
public ConfigPropertySource(String name, Config source) {
ConfigPropertySource(String name, Config source) {
super(name, source);
}
......@@ -31,4 +32,8 @@ public class ConfigPropertySource extends EnumerablePropertySource<Config> {
public Object getProperty(String name) {
return this.source.getProperty(name, null);
}
public void addChangeListener(ConfigChangeListener listener) {
this.source.addChangeListener(listener);
}
}
package com.ctrip.framework.apollo.spring.config;
import java.util.List;
import com.ctrip.framework.apollo.Config;
import com.google.common.collect.Lists;
public class ConfigPropertySourceFactory {
private final List<ConfigPropertySource> configPropertySources = Lists.newLinkedList();
public ConfigPropertySource getConfigPropertySource(String name, Config source) {
ConfigPropertySource configPropertySource = new ConfigPropertySource(name, source);
configPropertySources.add(configPropertySource);
return configPropertySource;
}
public List<ConfigPropertySource> getAllConfigPropertySources() {
return Lists.newLinkedList(configPropertySources);
}
}
package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
......@@ -22,5 +24,19 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
PropertySourcesPlaceholderConfigurer.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
processSpringValueDefinition(registry);
}
/**
* For Spring 3.x versions, the BeanDefinitionRegistryPostProcessor would not be
* instantiated if it is added in postProcessBeanDefinitionRegistry phase, so we have to manually
* call the postProcessBeanDefinitionRegistry method of SpringValueDefinitionProcessor here...
*/
private void processSpringValueDefinition(BeanDefinitionRegistry registry) {
SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
......@@ -33,6 +34,8 @@ import java.util.Iterator;
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create();
private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector
.getInstance(ConfigPropertySourceFactory.class);
private ConfigurableEnvironment environment;
public static boolean addNamespaces(Collection<String> namespaces, int order) {
......@@ -60,7 +63,7 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
for (String namespace : NAMESPACE_NAMES.get(order)) {
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(new ConfigPropertySource(namespace, config));
composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
}
......
package com.ctrip.framework.apollo.spring.property;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.Stack;
import org.springframework.util.StringUtils;
/**
* Extract keys from placeholder, e.g.
* <ul>
* <li>${some.key} => "some.key"</li>
* <li>${some.key:${some.other.key:100}} => "some.key", "some.other.key"</li>
* <li>${${some.key}} => "some.key"</li>
* <li>${${some.key:other.key}} => "some.key"</li>
* <li>${${some.key}:${another.key}} => "some.key", "another.key"</li>
* <li>#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"</li>
* </ul>
*/
public class PlaceholderHelper {
private static final String PLACEHOLDER_PREFIX = "${";
private static final String PLACEHOLDER_SUFFIX = "}";
private static final String VALUE_SEPARATOR = ":";
private static final String SIMPLE_PLACEHOLDER_PREFIX = "{";
private static final String EXPRESSION_PREFIX = "#{";
private static final String EXPRESSION_SUFFIX = "}";
public Set<String> extractPlaceholderKeys(String propertyString) {
Set<String> placeholderKeys = Sets.newHashSet();
if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) {
return placeholderKeys;
}
Stack<String> stack = new Stack<>();
stack.push(propertyString);
while (!stack.isEmpty()) {
String strVal = stack.pop();
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
if (startIndex == -1) {
placeholderKeys.add(strVal);
continue;
}
int endIndex = findPlaceholderEndIndex(strVal, startIndex);
if (endIndex == -1) {
// invalid placeholder?
continue;
}
String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
// ${some.key:other.key}
if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
stack.push(placeholderCandidate);
} else {
// some.key:${some.other.key:100}
int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);
if (separatorIndex == -1) {
stack.push(placeholderCandidate);
} else {
stack.push(placeholderCandidate.substring(0, separatorIndex));
String defaultValuePart =
normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
if (!Strings.isNullOrEmpty(defaultValuePart)) {
stack.push(defaultValuePart);
}
}
}
// has remaining part, e.g. ${a}.${b}
if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
if (!Strings.isNullOrEmpty(remainingPart)) {
stack.push(remainingPart);
}
}
}
return placeholderKeys;
}
private boolean isNormalizedPlaceholder(String propertyString) {
return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.endsWith(PLACEHOLDER_SUFFIX);
}
private boolean isExpressionWithPlaceholder(String propertyString) {
return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.endsWith(EXPRESSION_SUFFIX)
&& propertyString.contains(PLACEHOLDER_PREFIX);
}
private String normalizeToPlaceholder(String strVal) {
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
if (startIndex == -1) {
return null;
}
int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX);
if (endIndex == -1) {
return null;
}
return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length());
}
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + PLACEHOLDER_PREFIX.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + PLACEHOLDER_SUFFIX.length();
} else {
return index;
}
} else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) {
withinNestedPlaceholder++;
index = index + SIMPLE_PLACEHOLDER_PREFIX.length();
} else {
index++;
}
}
return -1;
}
}
package com.ctrip.framework.apollo.spring.property;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.springframework.core.MethodParameter;
/**
* Spring @Value method info
*
* @author github.com/zhegexiaohuozi seimimaster@gmail.com
* @since 2018/2/6.
*/
public class SpringValue {
private MethodParameter methodParameter;
private Field field;
private Object bean;
private String beanName;
private String key;
private String placeholder;
private Class<?> targetType;
public SpringValue(String key, String placeholder, Object bean, String beanName, Field field) {
this.bean = bean;
this.beanName = beanName;
this.field = field;
this.key = key;
this.placeholder = placeholder;
this.targetType = field.getType();
}
public SpringValue(String key, String placeholder, Object bean, String beanName, Method method) {
this.bean = bean;
this.beanName = beanName;
this.methodParameter = new MethodParameter(method, 0);
this.key = key;
this.placeholder = placeholder;
Class<?>[] paramTps = method.getParameterTypes();
this.targetType = paramTps[0];
}
public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
if (isField()) {
injectField(newVal);
} else {
injectMethod(newVal);
}
}
private void injectField(Object newVal) throws IllegalAccessException {
boolean accessible = field.isAccessible();
field.setAccessible(true);
field.set(bean, newVal);
field.setAccessible(accessible);
}
private void injectMethod(Object newVal)
throws InvocationTargetException, IllegalAccessException {
methodParameter.getMethod().invoke(bean, newVal);
}
public String getBeanName() {
return beanName;
}
public Class<?> getTargetType() {
return targetType;
}
public String getPlaceholder() {
return this.placeholder;
}
public MethodParameter getMethodParameter() {
return methodParameter;
}
public boolean isField() {
return this.field != null;
}
public Field getField() {
return field;
}
@Override
public String toString() {
if (isField()) {
return String
.format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass().getName(), field.getName());
}
return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(),
methodParameter.getMethod().getName());
}
}
package com.ctrip.framework.apollo.spring.property;
public class SpringValueDefinition {
private final String key;
private final String placeholder;
private final String propertyName;
public SpringValueDefinition(String key, String placeholder, String propertyName) {
this.key = key;
this.placeholder = placeholder;
this.propertyName = propertyName;
}
public String getKey() {
return key;
}
public String getPlaceholder() {
return placeholder;
}
public String getPropertyName() {
return propertyName;
}
}
package com.ctrip.framework.apollo.spring.property;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
/**
* To process xml config placeholders, e.g.
*
* <pre>
* &lt;bean class=&quot;com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean&quot;&gt;
* &lt;property name=&quot;timeout&quot; value=&quot;${timeout:200}&quot;/&gt;
* &lt;property name=&quot;batch&quot; value=&quot;${batch:100}&quot;/&gt;
* &lt;/bean&gt;
* </pre>
*/
public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
private static final Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions =
LinkedListMultimap.create();
private static final AtomicBoolean initialized = new AtomicBoolean(false);
private final ConfigUtil configUtil;
private final PlaceholderHelper placeholderHelper;
public SpringValueDefinitionProcessor() {
configUtil = ApolloInjector.getInstance(ConfigUtil.class);
placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class);
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
processPropertyValues(registry);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
public static Multimap<String, SpringValueDefinition> getBeanName2SpringValueDefinitions() {
return beanName2SpringValueDefinitions;
}
private void processPropertyValues(BeanDefinitionRegistry beanRegistry) {
if (!initialized.compareAndSet(false, true)) {
// already initialized
return;
}
String[] beanNames = beanRegistry.getBeanDefinitionNames();
for (String beanName : beanNames) {
BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName);
MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
List<PropertyValue> propertyValues = mutablePropertyValues.getPropertyValueList();
for (PropertyValue propertyValue : propertyValues) {
Object value = propertyValue.getValue();
if (!(value instanceof TypedStringValue)) {
continue;
}
String placeholder = ((TypedStringValue) value).getValue();
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder);
if (keys.isEmpty()) {
continue;
}
for (String key : keys) {
beanName2SpringValueDefinitions.put(beanName,
new SpringValueDefinition(key, placeholder, propertyValue.getName()));
}
}
}
}
//only for test
private static void reset() {
initialized.set(false);
beanName2SpringValueDefinitions.clear();
}
}
......@@ -33,6 +33,7 @@ public class ConfigUtil {
private long configCacheExpireTime = 1;//1 minute
private TimeUnit configCacheExpireTimeUnit = TimeUnit.MINUTES;//1 minute
private long longPollingInitialDelayInMills = 2000;//2 seconds
private boolean autoUpdateInjectedSpringProperties = true;
public ConfigUtil() {
initRefreshInterval();
......@@ -42,6 +43,7 @@ public class ConfigUtil {
initQPS();
initMaxConfigCacheSize();
initLongPollingInitialDelayInMills();
initAutoUpdateInjectedSpringProperties();
}
/**
......@@ -263,4 +265,20 @@ public class ConfigUtil {
public long getLongPollingInitialDelayInMills() {
return longPollingInitialDelayInMills;
}
private void initAutoUpdateInjectedSpringProperties() {
// 1. Get from System Property
String enableAutoUpdate = System.getProperty("apollo.autoUpdateInjectedSpringProperties");
if (Strings.isNullOrEmpty(enableAutoUpdate)) {
// 2. Get from app.properties
enableAutoUpdate = Foundation.app().getProperty("apollo.autoUpdateInjectedSpringProperties", null);
}
if (!Strings.isNullOrEmpty(enableAutoUpdate)) {
autoUpdateInjectedSpringProperties = Boolean.parseBoolean(enableAutoUpdate.trim());
}
}
public boolean isAutoUpdateInjectedSpringPropertiesEnabled() {
return autoUpdateInjectedSpringProperties;
}
}
package com.ctrip.framework.apollo;
import com.ctrip.framework.apollo.spring.BootstrapConfigTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import com.ctrip.framework.apollo.integration.ConfigIntegrationTest;
import com.ctrip.framework.apollo.internals.DefaultConfigManagerTest;
import com.ctrip.framework.apollo.internals.DefaultConfigTest;
......@@ -19,24 +13,35 @@ import com.ctrip.framework.apollo.internals.XmlConfigFileTest;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManagerTest;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryTest;
import com.ctrip.framework.apollo.spi.DefaultConfigRegistryTest;
import com.ctrip.framework.apollo.spring.BootstrapConfigTest;
import com.ctrip.framework.apollo.spring.JavaConfigAnnotationTest;
import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderAutoUpdateTest;
import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest;
import com.ctrip.framework.apollo.spring.XMLConfigAnnotationTest;
import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderAutoUpdateTest;
import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceTest;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelperTest;
import com.ctrip.framework.apollo.util.ConfigUtilTest;
import com.ctrip.framework.apollo.util.ExceptionUtilTest;
import com.ctrip.framework.apollo.util.parser.DateParserTest;
import com.ctrip.framework.apollo.util.parser.DurationParserTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
ConfigServiceTest.class, DefaultConfigRegistryTest.class, DefaultConfigFactoryManagerTest.class,
DefaultConfigManagerTest.class, DefaultConfigTest.class, LocalFileConfigRepositoryTest.class,
RemoteConfigRepositoryTest.class, SimpleConfigTest.class, DefaultConfigFactoryTest.class,
ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class, PropertiesConfigFileTest.class,
RemoteConfigLongPollServiceTest.class, DateParserTest.class, DurationParserTest.class, JsonConfigFileTest.class,
XmlConfigPlaceholderTest.class, JavaConfigPlaceholderTest.class, XMLConfigAnnotationTest.class,
JavaConfigAnnotationTest.class, ConfigUtilTest.class, BootstrapConfigTest.class
ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class,
PropertiesConfigFileTest.class, RemoteConfigLongPollServiceTest.class, DateParserTest.class,
DurationParserTest.class, JsonConfigFileTest.class, XmlConfigPlaceholderTest.class,
JavaConfigPlaceholderTest.class, XMLConfigAnnotationTest.class, JavaConfigAnnotationTest.class,
ConfigUtilTest.class, BootstrapConfigTest.class, JavaConfigPlaceholderAutoUpdateTest.class,
XmlConfigPlaceholderAutoUpdateTest.class, ConfigPropertySourceTest.class,
PlaceholderHelperTest.class
})
public class AllTests {
......
......@@ -59,6 +59,5 @@ public class MockInjector implements Injector {
public static void reset() {
classMap.clear();
classTable.clear();
delegate = null;
}
}
package com.ctrip.framework.apollo.spring;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.internals.ConfigRepository;
import com.ctrip.framework.apollo.internals.SimpleConfig;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.util.ConfigUtil;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import org.junit.After;
import org.junit.Before;
import org.springframework.util.ReflectionUtils;
......@@ -22,12 +33,15 @@ import com.google.common.collect.Maps;
public abstract class AbstractSpringIntegrationTest {
private static final Map<String, Config> CONFIG_REGISTRY = Maps.newHashMap();
private static Method PROPERTY_SOURCES_PROCESSOR_CLEAR;
private static Method SPRING_VALUE_DEFINITION_PROCESS_CLEAR;
private static Method CONFIG_SERVICE_RESET;
static {
try {
PROPERTY_SOURCES_PROCESSOR_CLEAR = PropertySourcesProcessor.class.getDeclaredMethod("reset");
ReflectionUtils.makeAccessible(PROPERTY_SOURCES_PROCESSOR_CLEAR);
SPRING_VALUE_DEFINITION_PROCESS_CLEAR = SpringValueDefinitionProcessor.class.getDeclaredMethod("reset");
ReflectionUtils.makeAccessible(SPRING_VALUE_DEFINITION_PROCESS_CLEAR);
CONFIG_SERVICE_RESET = ConfigService.class.getDeclaredMethod("reset");
ReflectionUtils.makeAccessible(CONFIG_SERVICE_RESET);
} catch (NoSuchMethodException e) {
......@@ -45,6 +59,53 @@ public abstract class AbstractSpringIntegrationTest {
doTearDown();
}
protected SimpleConfig prepareConfig(String namespaceName, Properties properties) {
ConfigRepository configRepository = mock(ConfigRepository.class);
when(configRepository.getConfig()).thenReturn(properties);
SimpleConfig config = new SimpleConfig(ConfigConsts.NAMESPACE_APPLICATION, configRepository);
mockConfig(namespaceName, config);
return config;
}
protected Properties assembleProperties(String key, String value) {
Properties properties = new Properties();
properties.setProperty(key, value);
return properties;
}
protected Properties assembleProperties(String key, String value, String key2, String value2) {
Properties properties = new Properties();
properties.setProperty(key, value);
properties.setProperty(key2, value2);
return properties;
}
protected Properties assembleProperties(String key, String value, String key2, String value2,
String key3, String value3) {
Properties properties = new Properties();
properties.setProperty(key, value);
properties.setProperty(key2, value2);
properties.setProperty(key3, value3);
return properties;
}
protected Date assembleDate(int year, int month, int day, int hour, int minute, int second, int millisecond) {
Calendar date = Calendar.getInstance();
date.set(year, month - 1, day, hour, minute, second); //Month in Calendar is 0 based
date.set(Calendar.MILLISECOND, millisecond);
return date.getTime();
}
protected static void mockConfig(String namespace, Config config) {
CONFIG_REGISTRY.put(namespace, config);
}
......@@ -52,6 +113,8 @@ public abstract class AbstractSpringIntegrationTest {
protected static void doSetUp() {
//as PropertySourcesProcessor has some static states, so we must manually clear its state
ReflectionUtils.invokeMethod(PROPERTY_SOURCES_PROCESSOR_CLEAR, null);
//as SpringValueDefinitionProcessor has some static states, so we must manually clear its state
ReflectionUtils.invokeMethod(SPRING_VALUE_DEFINITION_PROCESS_CLEAR, null);
//as ConfigService is singleton, so we must manually clear its container
ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null);
MockInjector.reset();
......@@ -62,7 +125,7 @@ public abstract class AbstractSpringIntegrationTest {
CONFIG_REGISTRY.clear();
}
public static class MockConfigManager implements ConfigManager {
private static class MockConfigManager implements ConfigManager {
@Override
public Config getConfig(String namespace) {
......@@ -74,4 +137,18 @@ public abstract class AbstractSpringIntegrationTest {
return null;
}
}
protected static class MockConfigUtil extends ConfigUtil {
private boolean isAutoUpdateInjectedSpringProperties;
public void setAutoUpdateInjectedSpringProperties(boolean autoUpdateInjectedSpringProperties) {
isAutoUpdateInjectedSpringProperties = autoUpdateInjectedSpringProperties;
}
@Override
public boolean isAutoUpdateInjectedSpringPropertiesEnabled() {
return isAutoUpdateInjectedSpringProperties;
}
}
}
......@@ -88,7 +88,8 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
TestApolloConfigChangeListenerBean1 bean = getBean(TestApolloConfigChangeListenerBean1.class, AppConfig3.class);
assertEquals(3, applicationListeners.size());
//PropertySourcesProcessor add listeners to listen config changed of all namespace
assertEquals(4, applicationListeners.size());
assertEquals(1, fxApolloListeners.size());
for (ConfigChangeListener listener : applicationListeners) {
......
......@@ -7,15 +7,19 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -154,6 +158,25 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
}
@Test
public void testApplicationPropertySourceWithValueInjectedAsConstructorArgs() throws Exception {
int someTimeout = 1000;
int someBatch = 2000;
Config config = mock(Config.class);
when(config.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
when(config.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig7.class);
TestJavaConfigBean3 bean = context.getBean(TestJavaConfigBean3.class);
assertEquals(someTimeout, bean.getTimeout());
assertEquals(someBatch, bean.getBatch());
}
@Test
public void testNestedProperty() throws Exception {
String a = "a";
String b = "b";
......@@ -324,6 +347,14 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
}
@Configuration
@ComponentScan(
includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Component.class})},
excludeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Configuration.class})})
@EnableApolloConfig
static class AppConfig7 {
}
@Configuration
@EnableApolloConfig
static class NestedPropertyConfig1 {
@Bean
......@@ -332,8 +363,6 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
}
}
@Component
static class TestJavaConfigBean {
@Value("${timeout:100}")
private int timeout;
......@@ -374,6 +403,27 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
}
}
@Component
static class TestJavaConfigBean3 {
private final int timeout;
private final int batch;
@Autowired
public TestJavaConfigBean3(@Value("${timeout:100}") int timeout,
@Value("${batch:200}") int batch) {
this.timeout = timeout;
this.batch = batch;
}
public int getTimeout() {
return timeout;
}
public int getBatch() {
return batch;
}
}
static class TestNestedPropertyBean {
@Value("${${a}.${b}:${c:100}}")
......
......@@ -86,7 +86,8 @@ public class XMLConfigAnnotationTest extends AbstractSpringIntegrationTest {
TestApolloConfigChangeListenerBean1 bean = getBean("spring/XmlConfigAnnotationTest3.xml",
TestApolloConfigChangeListenerBean1.class);
assertEquals(3, applicationListeners.size());
//PropertySourcesProcessor add listeners to listen config changed of all namespace
assertEquals(4, applicationListeners.size());
assertEquals(1, fxApolloListeners.size());
for (ConfigChangeListener listener : applicationListeners) {
......
package com.ctrip.framework.apollo.spring.config;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
@RunWith(MockitoJUnitRunner.class)
public class ConfigPropertySourceTest {
private ConfigPropertySource configPropertySource;
@Mock
private Config someConfig;
@Before
public void setUp() throws Exception {
String someName = "someName";
configPropertySource = new ConfigPropertySource(someName, someConfig);
}
@Test
public void testGetPropertyNames() throws Exception {
String somePropertyName = "somePropertyName";
String anotherPropertyName = "anotherPropertyName";
Set<String> somePropertyNames = Sets.newHashSet(somePropertyName, anotherPropertyName);
when(someConfig.getPropertyNames()).thenReturn(somePropertyNames);
String[] result = configPropertySource.getPropertyNames();
verify(someConfig, times(1)).getPropertyNames();
assertArrayEquals(somePropertyNames.toArray(), result);
}
@Test
public void testGetEmptyPropertyNames() throws Exception {
when(someConfig.getPropertyNames()).thenReturn(Sets.<String>newHashSet());
assertEquals(0, configPropertySource.getPropertyNames().length);
}
@Test
public void testGetProperty() throws Exception {
String somePropertyName = "somePropertyName";
String someValue = "someValue";
when(someConfig.getProperty(somePropertyName, null)).thenReturn(someValue);
assertEquals(someValue, configPropertySource.getProperty(somePropertyName));
verify(someConfig, times(1)).getProperty(somePropertyName, null);
}
@Test
public void testAddChangeListener() throws Exception {
ConfigChangeListener someListener = mock(ConfigChangeListener.class);
ConfigChangeListener anotherListener = mock(ConfigChangeListener.class);
final List<ConfigChangeListener> listeners = Lists.newArrayList();
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
listeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class));
return Void.class;
}
}).when(someConfig).addChangeListener(any(ConfigChangeListener.class));
configPropertySource.addChangeListener(someListener);
configPropertySource.addChangeListener(anotherListener);
assertEquals(2, listeners.size());
assertTrue(listeners.containsAll(Lists.newArrayList(someListener, anotherListener)));
}
}
package com.ctrip.framework.apollo.spring.property;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Test;
public class PlaceholderHelperTest {
private PlaceholderHelper placeholderHelper;
@Before
public void setUp() throws Exception {
placeholderHelper = new PlaceholderHelper();
}
@Test
public void testExtractPlaceholderKeys() throws Exception {
check("${some.key}", "some.key");
check("${some.key:100}", "some.key");
check("${some.key:${some.other.key}}", "some.key", "some.other.key");
check("${some.key:${some.other.key:100}}", "some.key", "some.other.key");
}
@Test
public void testExtractNestedPlaceholderKeys() throws Exception {
check("${${some.key}}", "some.key");
check("${${some.key:other.key}}", "some.key");
check("${${some.key}:100}", "some.key");
check("${${some.key}:${another.key}}", "some.key", "another.key");
}
@Test
public void testExtractComplexNestedPlaceholderKeys() throws Exception {
check("${${a}1${b}:3.${c:${d:100}}}", "a", "b", "c", "d");
check("${1${a}2${b}3:4.${c:5${d:100}6}7}", "a", "b", "c", "d");
}
@Test
public void testExtractPlaceholderKeysFromExpression() throws Exception {
check("#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')}", "some.key", "another.key");
check("#{new java.text.SimpleDateFormat('${some.key:abc}').parse('${another.key:100}')}", "some.key", "another.key");
check("#{new java.text.SimpleDateFormat('${some.key:${some.other.key}}').parse('${another.key}')}", "some.key", "another.key", "some.other.key");
check("#{new java.text.SimpleDateFormat('${some.key:${some.other.key:abc}}').parse('${another.key}')}", "some.key", "another.key", "some.other.key");
check("#{new java.text.SimpleDateFormat('${${some.key}}').parse('${${another.key:other.key}}')}", "some.key", "another.key");
assertTrue(placeholderHelper.extractPlaceholderKeys("#{systemProperties[some.key] ?: 123}").isEmpty());
assertTrue(placeholderHelper.extractPlaceholderKeys("#{ T(java.lang.Math).random() * 100.0 }").isEmpty());
}
@Test
public void testExtractInvalidPlaceholderKeys() throws Exception {
assertTrue(placeholderHelper.extractPlaceholderKeys("some.key").isEmpty());
assertTrue(placeholderHelper.extractPlaceholderKeys("some.key:100").isEmpty());
}
private void check(String propertyString, String... expectedPlaceholders) {
assertEquals(Sets.newHashSet(expectedPlaceholders), placeholderHelper.extractPlaceholderKeys(propertyString));
}
}
......@@ -3,7 +3,6 @@ package com.ctrip.framework.apollo.util;
import com.ctrip.framework.apollo.core.ConfigConsts;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
......@@ -22,6 +21,7 @@ public class ConfigUtilTest {
System.clearProperty("apollo.longPollQPS");
System.clearProperty("apollo.configCacheSize");
System.clearProperty("apollo.longPollingInitialDelayInMills");
System.clearProperty("apollo.autoUpdateInjectedSpringProperties");
}
@Test
......@@ -173,4 +173,16 @@ public class ConfigUtilTest {
assertTrue(configUtil.getLongPollingInitialDelayInMills() > 0);
}
}
\ No newline at end of file
@Test
public void testCustomizeAutoUpdateInjectedSpringProperties() throws Exception {
boolean someAutoUpdateInjectedSpringProperties = false;
System.setProperty("apollo.autoUpdateInjectedSpringProperties",
String.valueOf(someAutoUpdateInjectedSpringProperties));
ConfigUtil configUtil = new ConfigUtil();
assertEquals(someAutoUpdateInjectedSpringProperties,
configUtil.isAutoUpdateInjectedSpringPropertiesEnabled());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<apollo:config />
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderAutoUpdateTest.TestAllKindsOfDataTypesBean">
<property name="intProperty" value="${intProperty}"/>
<property name="intArrayProperty" value="${intArrayProperty}"/>
<property name="longProperty" value="${longProperty}"/>
<property name="shortProperty" value="${shortProperty}"/>
<property name="floatProperty" value="${floatProperty}"/>
<property name="doubleProperty" value="${doubleProperty}"/>
<property name="byteProperty" value="${byteProperty}"/>
<property name="booleanProperty" value="${booleanProperty}"/>
<property name="stringProperty" value="${stringProperty}"/>
<property name="dateProperty" value="#{new java.text.SimpleDateFormat('${dateFormat}').parse('${dateProperty}')}"/>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<apollo:config />
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean">
<property name="timeout" value="${timeout}"/>
<property name="batch" value="${batch}"/>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<apollo:config />
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderAutoUpdateTest.TestXmlBeanWithConstructorArgs">
<constructor-arg index="0" value="${timeout}"/>
<constructor-arg index="1" value="${batch}"/>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<context:annotation-config />
<apollo:config/>
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderAutoUpdateTest.TestXmlBeanWithInjectedValue">
<property name="batch" value="${batch}"/>
</bean>
</beans>
......@@ -46,7 +46,7 @@ public class ApolloConfigDemo {
};
config = ConfigService.getAppConfig();
config.addChangeListener(changeListener);
publicConfig = ConfigService.getConfig("FX.apollo");
publicConfig = ConfigService.getConfig("TEST1.apollo");
publicConfig.addChangeListener(changeListener);
applicationConfigFile = ConfigService.getConfigFile("application", ConfigFileFormat.Properties);
xmlConfigFile = ConfigService.getConfigFile("datasources", ConfigFileFormat.XML);
......
......@@ -3,34 +3,29 @@ package com.ctrip.framework.apollo.demo.spring.common.bean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RefreshScope
@Component("annotatedBean")
public class AnnotatedBean {
private static final Logger logger = LoggerFactory.getLogger(AnnotatedBean.class);
@Value("${timeout:200}")
private int timeout;
private int batch;
@PostConstruct
void initialize() {
logger.info("timeout is initialized as {}", timeout);
logger.info("batch is initialized as {}", batch);
}
@Value("${batch:100}")
public void setBatch(int batch) {
logger.info("updating batch, old value: {}, new value: {}", this.batch, batch);
this.batch = batch;
}
@Value("${timeout:200}")
public void setTimeout(int timeout) {
logger.info("updating timeout, old value: {}, new value: {}", this.timeout, timeout);
this.timeout = timeout;
}
@Override
public String toString() {
......
......@@ -8,6 +8,6 @@ import org.springframework.context.annotation.Configuration;
* @author Jason Song(song_s@ctrip.com)
*/
@Configuration
@EnableApolloConfig(value = "FX.apollo", order = 11)
@EnableApolloConfig(value = "TEST1.apollo", order = 11)
public class AnotherAppConfig {
}
package com.ctrip.framework.apollo.demo.spring.common.refresh;
import com.ctrip.framework.apollo.demo.spring.common.bean.AnnotatedBean;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.stereotype.Component;
/**
* To refresh the config bean when config is changed
*
* @author Jason Song(song_s@ctrip.com)
*/
@Component
public class ApolloRefreshConfig {
private static final Logger logger = LoggerFactory.getLogger(ApolloRefreshConfig.class);
@Autowired
private RefreshScope refreshScope;
@Autowired
private AnnotatedBean annotatedBean;
@ApolloConfigChangeListener({"application", "FX.apollo"})
private void onChange(ConfigChangeEvent changeEvent) {
if (changeEvent.isChanged("timeout") || changeEvent.isChanged("batch")) {
logger.info("before refresh {}", annotatedBean.toString());
//could also call refreshScope.refreshAll();
refreshScope.refresh("annotatedBean");
logger.info("after refresh {}", annotatedBean.toString());
}
}
}
package com.ctrip.framework.apollo.demo.spring.javaConfigDemo;
import com.ctrip.framework.apollo.demo.spring.common.bean.AnnotatedBean;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Scanner;
......@@ -8,14 +15,19 @@ import java.util.Scanner;
* @author Jason Song(song_s@ctrip.com)
*/
public class AnnotationApplication {
public static void main(String[] args) {
new AnnotationConfigApplicationContext("com.ctrip.framework.apollo.demo.spring.common",
"com.ctrip.framework.apollo.demo.spring.javaConfigDemo");
onKeyExit();
}
public static void main(String[] args) throws IOException {
ApplicationContext context = new AnnotationConfigApplicationContext("com.ctrip.framework.apollo.demo.spring.common");
AnnotatedBean annotatedBean = context.getBean(AnnotatedBean.class);
System.out.println("AnnotationApplication Demo. Input any key except quit to print the values. Input quit to exit.");
while (true) {
System.out.print("> ");
String input = new BufferedReader(new InputStreamReader(System.in, Charsets.UTF_8)).readLine();
if (!Strings.isNullOrEmpty(input) && input.trim().equalsIgnoreCase("quit")) {
System.exit(0);
}
private static void onKeyExit() {
System.out.println("Press Enter to exit...");
new Scanner(System.in).nextLine();
System.out.println(annotatedBean.toString());
}
}
}
package com.ctrip.framework.apollo.demo.spring.javaConfigDemo.config;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* to support RefreshScope
* @author Jason Song(song_s@ctrip.com)
*/
@Configuration
@Import(RefreshAutoConfiguration.class)
public class RefreshScopeConfig {
}
package com.ctrip.framework.apollo.demo.spring.springBootDemo;
import com.ctrip.framework.apollo.demo.spring.springBootDemo.config.SampleRedisConfig;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationContext;
import java.util.Scanner;
import com.ctrip.framework.apollo.demo.spring.common.bean.AnnotatedBean;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -13,13 +21,21 @@ import java.util.Scanner;
})
public class SpringBootSampleApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootSampleApplication.class).run(args);
onKeyExit();
}
public static void main(String[] args) throws IOException {
ApplicationContext context = new SpringApplicationBuilder(SpringBootSampleApplication.class).run(args);
AnnotatedBean annotatedBean = context.getBean(AnnotatedBean.class);
SampleRedisConfig redisConfig = context.getBean(SampleRedisConfig.class);
System.out.println("SpringBootSampleApplication Demo. Input any key except quit to print the values. Input quit to exit.");
while (true) {
System.out.print("> ");
String input = new BufferedReader(new InputStreamReader(System.in, Charsets.UTF_8)).readLine();
if (!Strings.isNullOrEmpty(input) && input.trim().equalsIgnoreCase("quit")) {
System.exit(0);
}
private static void onKeyExit() {
System.out.println("Press Enter to exit...");
new Scanner(System.in).nextLine();
System.out.println(annotatedBean.toString());
System.out.println(redisConfig.toString());
}
}
}
package com.ctrip.framework.apollo.demo.spring.springBootDemo.refresh;
import com.ctrip.framework.apollo.demo.spring.common.refresh.ApolloRefreshConfig;
import com.ctrip.framework.apollo.demo.spring.springBootDemo.config.SampleRedisConfig;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -12,6 +7,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.stereotype.Component;
import com.ctrip.framework.apollo.demo.spring.springBootDemo.config.SampleRedisConfig;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
/**
* @author Jason Song(song_s@ctrip.com)
*/
......@@ -21,9 +20,6 @@ public class SpringBootApolloRefreshConfig {
private static final Logger logger = LoggerFactory.getLogger(SpringBootApolloRefreshConfig.class);
@Autowired
private ApolloRefreshConfig apolloRefreshConfig;
@Autowired
private SampleRedisConfig sampleRedisConfig;
@Autowired
......@@ -31,6 +27,17 @@ public class SpringBootApolloRefreshConfig {
@ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) {
boolean redisCacheKeysChanged = false;
for (String changedKey : changeEvent.changedKeys()) {
if (changedKey.startsWith("redis.cache")) {
redisCacheKeysChanged = true;
break;
}
}
if (!redisCacheKeysChanged) {
return;
}
logger.info("before refresh {}", sampleRedisConfig.toString());
refreshScope.refresh("sampleRedisConfig");
logger.info("after refresh {}", sampleRedisConfig.toString());
......
package com.ctrip.framework.apollo.demo.spring.xmlConfigDemo;
import com.google.common.base.Strings;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Scanner;
import com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean;
import com.google.common.base.Charsets;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class XmlApplication {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("spring.xml");
onKeyExit();
}
public static void main(String[] args) throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
XmlBean xmlBean = context.getBean(XmlBean.class);
System.out.println("XmlApplication Demo. Input any key except quit to print the values. Input quit to exit.");
while (true) {
System.out.print("> ");
String input = new BufferedReader(new InputStreamReader(System.in, Charsets.UTF_8)).readLine();
if (!Strings.isNullOrEmpty(input) && input.trim().equalsIgnoreCase("quit")) {
System.exit(0);
}
private static void onKeyExit() {
System.out.println("Press Enter to exit...");
new Scanner(System.in).nextLine();
System.out.println(xmlBean.toString());
}
}
}
......@@ -29,4 +29,9 @@ public class XmlBean {
public int getBatch() {
return batch;
}
@Override
public String toString() {
return String.format("[XmlBean] timeout: %d, batch: %d", timeout, batch);
}
}
package com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.refresh;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ManualRefreshUtil {
private static final Logger logger = LoggerFactory.getLogger(ManualRefreshUtil.class);
@ApolloConfig
private Config config;
@Autowired
private XmlBean xmlBean;
@ApolloConfigChangeListener
private void onChange(ConfigChangeEvent changeEvent) {
if (changeEvent.isChanged("timeout")) {
logger.info("Manually refreshing xmlBean.timeout");
xmlBean.setTimeout(config.getIntProperty("timeout", xmlBean.getTimeout()));
}
if (changeEvent.isChanged("batch")) {
logger.info("Manually refreshing xmlBean.batch");
xmlBean.setBatch(config.getIntProperty("batch", xmlBean.getBatch()));
}
}
}
apollo:
bootstrap:
enabled: true
# will inject 'application' and 'FX.apollo' namespaces in bootstrap phase
namespaces: application,FX.apollo
# will inject 'application' and 'TEST1.apollo' namespaces in bootstrap phase
namespaces: application,TEST1.apollo
......@@ -7,18 +7,12 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<apollo:config order="10"/>
<apollo:config namespaces="FX.apollo" order="11"/>
<apollo:config namespaces="TEST1.apollo" order="11"/>
<bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean">
<property name="timeout" value="${timeout:200}"/>
<property name="batch" value="${batch:100}"/>
</bean>
<bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.refresh.ManualRefreshUtil"/>
<!-- to support RefreshScope -->
<bean class="org.springframework.cloud.autoconfigure.RefreshAutoConfiguration"/>
<context:component-scan
base-package="com.ctrip.framework.apollo.demo.spring.common.bean,com.ctrip.framework.apollo.demo.spring.common.refresh"/>
<context:annotation-config />
</beans>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment