提交 ee51eff4 编写于 作者: J Jason Song

Apollo client integration with Spring

上级 87129adc
......@@ -32,6 +32,12 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- optional spring dependency -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<optional>true</optional>
</dependency>
<!-- test -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
......
package com.ctrip.framework.apollo.spring.annotation;
import com.google.common.base.Preconditions;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Apollo Annotation Processor for Spring Application
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ApolloAnnotationProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class clazz = bean.getClass();
processFields(bean, clazz.getDeclaredFields());
processMethods(bean, clazz.getDeclaredMethods());
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private void processFields(Object bean, Field[] declaredFields) {
for (Field field : declaredFields) {
ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
if (annotation == null) {
continue;
}
Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()),
"Invalid type: %s for field: %s, should be Config", field.getType(), field);
String namespace = annotation.value();
Config config = ConfigService.getConfig(namespace);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, bean, config);
}
}
private void processMethods(final Object bean, Method[] declaredMethods) {
for (final Method method : declaredMethods) {
ApolloConfigChangeListener annotation = AnnotationUtils.findAnnotation(method, ApolloConfigChangeListener.class);
if (annotation == null) {
continue;
}
Class<?>[] parameterTypes = method.getParameterTypes();
Preconditions.checkArgument(parameterTypes.length == 1,
"Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, method);
Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
"Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], method);
ReflectionUtils.makeAccessible(method);
String[] namespaces = annotation.value();
for (String namespace : namespaces) {
Config config = ConfigService.getConfig(namespace);
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
});
}
}
}
}
package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.core.ConfigConsts;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Use this annotation to inject Apollo Config Instance.
*
* <p>Usage example:</p>
* <pre class="code">
* //Inject the config for "someNamespace"
* &#064;ApolloConfig("someNamespace")
* private Config config;
* </pre>
*
* @author Jason Song(song_s@ctrip.com)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface ApolloConfig {
/**
* Apollo namespace for the config, if not specified then default to application
*/
String value() default ConfigConsts.NAMESPACE_APPLICATION;
}
package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.core.ConfigConsts;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Use this annotation to register Apollo ConfigChangeListener.
*
* <p>Usage example:</p>
* <pre class="code">
* //Listener on namespaces of "someNamespace" and "anotherNamespace"
* &#064;ApolloConfigChangeListener({"someNamespace","anotherNamespace"})
* private void onChange(ConfigChangeEvent changeEvent) {
* //handle change event
* }
* </pre>
*
* @author Jason Song(song_s@ctrip.com)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ApolloConfigChangeListener {
/**
* Apollo namespace for the config, if not specified then default to application
*/
String[] value() default {ConfigConsts.NAMESPACE_APPLICATION};
}
package com.ctrip.framework.apollo.spring.annotation;
import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata
.getAnnotationAttributes(EnableApolloConfig.class.getName()));
String[] namespaces = attributes.getStringArray("value");
int order = attributes.getNumber("order");
PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
PropertySourcesProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class);
}
}
package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.core.ConfigConsts;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Use this annotation to register Apollo property sources when using Java Config.
*
* <p>Configuration example:</p>
* <pre class="code">
* &#064;Configuration
* &#064;EnableApolloConfig({"someNamespace","anotherNamespace"})
* public class AppConfig {
*
* }
* </pre>
*
* @author Jason Song(song_s@ctrip.com)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ApolloConfigRegistrar.class)
public @interface EnableApolloConfig {
/**
* Apollo namespaces to inject configuration into Spring Property Sources.
*/
String[] value() default {ConfigConsts.NAMESPACE_APPLICATION};
/**
* The order of the apollo config, default is {@link Ordered#LOWEST_PRECEDENCE}, which is Integer.MAX_VALUE.
* If there are properties with the same name in different apollo configs, the apollo config with smaller order wins.
* @return
*/
int order() default Ordered.LOWEST_PRECEDENCE;
}
package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.Config;
import org.springframework.core.env.PropertySource;
/**
* Property source wrapper for Config
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigPropertySource extends PropertySource<Config> {
public ConfigPropertySource(String name, Config source) {
super(name, source);
}
@Override
public Object getProperty(String name) {
return this.source.getProperty(name, null);
}
}
package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor;
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
/**
* Apollo Property Sources processor for Spring XML Based Application
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class);
}
}
package com.ctrip.framework.apollo.spring.config;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.ctrip.framework.apollo.core.ConfigConsts;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.core.Ordered;
import org.w3c.dom.Element;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class NamespaceHandler extends NamespaceHandlerSupport {
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
@Override
public void init() {
registerBeanDefinitionParser("config", new BeanParser());
}
static class BeanParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return ConfigPropertySourcesProcessor.class;
}
@Override
protected boolean shouldGenerateId() {
return true;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String namespaces = element.getAttribute("namespaces");
//default to application
if (Strings.isNullOrEmpty(namespaces)) {
namespaces = ConfigConsts.NAMESPACE_APPLICATION;
}
int order = Ordered.LOWEST_PRECEDENCE;
String orderAttribute = element.getAttribute("order");
if (!Strings.isNullOrEmpty(orderAttribute)) {
try {
order = Integer.parseInt(orderAttribute);
} catch (Throwable ex) {
throw new IllegalArgumentException(
String.format("Invalid order: %s for namespaces: %s", orderAttribute, namespaces));
}
}
PropertySourcesProcessor.addNamespaces(NAMESPACE_SPLITTER.splitToList(namespaces), order);
}
}
}
package com.ctrip.framework.apollo.spring.config;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Multimap;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Apollo Property Sources processor for Spring Annotation Based Application
*
* @author Jason Song(song_s@ctrip.com)
*/
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
private static final String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
private static final Multimap<Integer, String> NAMESPACE_NAMES = HashMultimap.create();
private static final AtomicBoolean PROPERTY_SOURCES_INITIALIZED = new AtomicBoolean(false);
private ConfigurableEnvironment environment;
public static boolean addNamespaces(Collection<String> namespaces, int order) {
return NAMESPACE_NAMES.putAll(order, namespaces);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (!PROPERTY_SOURCES_INITIALIZED.compareAndSet(false, true)) {
//already initialized
return;
}
initializePropertySources();
}
protected void initializePropertySources() {
CompositePropertySource composite = new CompositePropertySource(APOLLO_PROPERTY_SOURCE_NAME);
//sort by order asc
ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());
Iterator<Integer> iterator = orders.iterator();
while (iterator.hasNext()) {
int order = iterator.next();
for (String namespace : NAMESPACE_NAMES.get(order)) {
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(new ConfigPropertySource(namespace, config));
}
}
environment.getPropertySources().addFirst(composite);
}
@Override
public void setEnvironment(Environment environment) {
//it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
this.environment = (ConfigurableEnvironment) environment;
}
//only for test
private static void reset() {
NAMESPACE_NAMES.clear();
PROPERTY_SOURCES_INITIALIZED.set(false);
}
}
/**
* This package contains Apollo Spring integration codes and enables the following features:<br/>
* <p>1. Support Spring XML based configuration</p>
* <ul>
* <li>&lt;apollo:config namespaces="someNamespace"/&gt; to inject configurations from Apollo into Spring Property
* Sources so that placeholders like ${someProperty} and @Value("someProperty") are supported.</li>
* </ul>
* <p>2. Support Spring Java based configuration</p>
* <ul>
* <li>@EnableApolloConfig(namespaces={"someNamespace"}) to inject configurations from Apollo into Spring Property
* Sources so that placeholders like ${someProperty} and @Value("someProperty") are supported.</li>
* </ul>
*
* With the above configuration, annotations like @ApolloConfig("someNamespace")
* and @ApolloConfigChangeListener("someNamespace) are also supported.<br />
* <br />
* Requires Spring 3.1.1+
*/
package com.ctrip.framework.apollo.spring;
\ No newline at end of file
package com.ctrip.framework.apollo.spring.util;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import java.util.Objects;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class BeanRegistrationUtil {
public static boolean registerBeanDefinitionIfNotExists(BeanDefinitionRegistry registry, String beanName,
Class<?> beanClass) {
if (registry.containsBeanDefinition(beanName)) {
return false;
}
String[] candidates = registry.getBeanDefinitionNames();
for (String candidate : candidates) {
BeanDefinition beanDefinition = registry.getBeanDefinition(candidate);
if (Objects.equals(beanDefinition.getBeanClassName(), beanClass.getName())) {
return false;
}
}
BeanDefinition annotationProcessor = BeanDefinitionBuilder.genericBeanDefinition(beanClass).getBeanDefinition();
registry.registerBeanDefinition(beanName, annotationProcessor);
return true;
}
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.ctrip.com/schema/apollo"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.ctrip.com/schema/apollo"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:annotation>
<xsd:documentation><![CDATA[ Namespace support for Ctrip Apollo Configuration Center. ]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="config">
<xsd:annotation>
<xsd:documentation>
<![CDATA[ Apollo configuration section to integrate with Spring.]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="namespaces" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The comma-separated list of namespace names to integrate with Spring property sources.
If not specified, then default to application namespace.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:int" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The order of the config, default to Ordered.LOWEST_PRECEDENCE, which is Integer.MAX_VALUE.
If there are properties with the same name in different apollo configs, the config with smaller order wins.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
\ No newline at end of file
http\://www.ctrip.com/schema/apollo=com.ctrip.framework.apollo.spring.config.NamespaceHandler
\ No newline at end of file
http\://www.ctrip.com/schema/apollo-1.0.0.xsd=/META-INF/apollo-1.0.0.xsd
http\://www.ctrip.com/schema/apollo.xsd=/META-INF/apollo-1.0.0.xsd
......@@ -14,6 +14,10 @@ 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.JavaConfigAnnotationTest;
import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest;
import com.ctrip.framework.apollo.spring.XMLConfigAnnotationTest;
import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest;
import com.ctrip.framework.apollo.util.ExceptionUtilTest;
import com.ctrip.framework.apollo.util.parser.DateParserTest;
import com.ctrip.framework.apollo.util.parser.DurationParserTest;
......@@ -27,9 +31,10 @@ import org.junit.runners.Suite.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
ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class, PropertiesConfigFileTest.class,
RemoteConfigLongPollServiceTest.class, DateParserTest.class, DurationParserTest.class, JsonConfigFileTest.class,
XmlConfigPlaceholderTest.class, JavaConfigPlaceholderTest.class, XMLConfigAnnotationTest.class,
JavaConfigAnnotationTest.class
})
public class AllTests {
......
......@@ -28,7 +28,7 @@ public class DefaultConfigManagerTest extends ComponentTestCase {
public void setUp() throws Exception {
super.tearDown();//clear the container
super.setUp();
defineComponent(ConfigFactoryManager.class, MockConfigManager.class);
defineComponent(ConfigFactoryManager.class, MockConfigFactoryManager.class);
defaultConfigManager = (DefaultConfigManager) lookup(ConfigManager.class);
someConfigContent = "someContent";
}
......@@ -84,7 +84,7 @@ public class DefaultConfigManagerTest extends ComponentTestCase {
}
public static class MockConfigManager implements ConfigFactoryManager {
public static class MockConfigFactoryManager implements ConfigFactoryManager {
@Override
public ConfigFactory getFactory(String namespace) {
......
......@@ -225,7 +225,7 @@ public class RemoteConfigLongPollServiceTest extends ComponentTestCase {
remoteConfigLongPollService.submit(anotherNamespace, anotherRepository);
submitAnotherNamespaceFinish.set(true);
onAnotherRepositoryNotified.get(500, TimeUnit.MILLISECONDS);
onAnotherRepositoryNotified.get(5000, TimeUnit.MILLISECONDS);
remoteConfigLongPollService.stopLongPollingRefresh();
......
package com.ctrip.framework.apollo.spring;
import com.google.common.collect.Maps;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.internals.ConfigManager;
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
import org.codehaus.plexus.PlexusContainer;
import org.junit.After;
import org.junit.Before;
import org.springframework.util.ReflectionUtils;
import org.unidal.lookup.ComponentTestCase;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public abstract class AbstractSpringIntegrationTest extends ComponentTestCase {
private static final Map<String, Config> CONFIG_REGISTRY = Maps.newHashMap();
private static Method PROPERTY_SOURCES_PROCESSOR_CLEAR;
private static Method CONFIG_SERVICE_SET_CONTAINER;
static {
try {
PROPERTY_SOURCES_PROCESSOR_CLEAR = PropertySourcesProcessor.class.getDeclaredMethod("reset");
ReflectionUtils.makeAccessible(PROPERTY_SOURCES_PROCESSOR_CLEAR);
CONFIG_SERVICE_SET_CONTAINER = ConfigService.class.getDeclaredMethod("setContainer", PlexusContainer.class);
ReflectionUtils.makeAccessible(CONFIG_SERVICE_SET_CONTAINER);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
@Before
public void setUp() throws Exception {
super.tearDown();//clear the container
super.setUp();
//as PropertySourcesProcessor has some static states, so we must manually clear its state
ReflectionUtils.invokeMethod(PROPERTY_SOURCES_PROCESSOR_CLEAR, null);
//as ConfigService is singleton, so we must manually clear its container
ReflectionUtils.invokeMethod(CONFIG_SERVICE_SET_CONTAINER, null, getContainer());
defineComponent(ConfigManager.class, MockConfigManager.class);
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
CONFIG_REGISTRY.clear();
}
protected void mockConfig(String namespace, Config config) {
CONFIG_REGISTRY.put(namespace, config);
}
public static class MockConfigManager implements ConfigManager {
@Override
public Config getConfig(String namespace) {
return CONFIG_REGISTRY.get(namespace);
}
@Override
public ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
}
}
package com.ctrip.framework.apollo.spring;
import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
@Test
public void testApolloConfig() throws Exception {
Config applicationConfig = mock(Config.class);
Config fxApolloConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
mockConfig(FX_APOLLO_NAMESPACE, fxApolloConfig);
TestApolloConfigBean1 bean = getBean(TestApolloConfigBean1.class, AppConfig1.class);
assertEquals(applicationConfig, bean.getConfig());
assertEquals(applicationConfig, bean.getAnotherConfig());
assertEquals(fxApolloConfig, bean.getYetAnotherConfig());
}
@Test(expected = BeanCreationException.class)
public void testApolloConfigWithWrongFieldType() throws Exception {
Config applicationConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
getBean(TestApolloConfigBean2.class, AppConfig2.class);
}
@Test
public void testApolloConfigChangeListener() throws Exception {
Config applicationConfig = mock(Config.class);
Config fxApolloConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
mockConfig(FX_APOLLO_NAMESPACE, fxApolloConfig);
final List<ConfigChangeListener> applicationListeners = Lists.newArrayList();
final List<ConfigChangeListener> fxApolloListeners = Lists.newArrayList();
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
applicationListeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class));
return Void.class;
}
}).when(applicationConfig).addChangeListener(any(ConfigChangeListener.class));
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
fxApolloListeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class));
return Void.class;
}
}).when(fxApolloConfig).addChangeListener(any(ConfigChangeListener.class));
ConfigChangeEvent someEvent = mock(ConfigChangeEvent.class);
ConfigChangeEvent anotherEvent = mock(ConfigChangeEvent.class);
TestApolloConfigChangeListenerBean1 bean = getBean(TestApolloConfigChangeListenerBean1.class, AppConfig3.class);
assertEquals(3, applicationListeners.size());
assertEquals(1, fxApolloListeners.size());
for (ConfigChangeListener listener : applicationListeners) {
listener.onChange(someEvent);
}
assertEquals(someEvent, bean.getChangeEvent1());
assertEquals(someEvent, bean.getChangeEvent2());
assertEquals(someEvent, bean.getChangeEvent3());
for (ConfigChangeListener listener : fxApolloListeners) {
listener.onChange(anotherEvent);
}
assertEquals(someEvent, bean.getChangeEvent1());
assertEquals(someEvent, bean.getChangeEvent2());
assertEquals(anotherEvent, bean.getChangeEvent3());
}
@Test(expected = BeanCreationException.class)
public void testApolloConfigChangeListenerWithWrongParamType() throws Exception {
Config applicationConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
getBean(TestApolloConfigChangeListenerBean2.class, AppConfig4.class);
}
@Test(expected = BeanCreationException.class)
public void testApolloConfigChangeListenerWithWrongParamCount() throws Exception {
Config applicationConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
getBean(TestApolloConfigChangeListenerBean3.class, AppConfig5.class);
}
private <T> T getBean(Class<T> beanClass, Class<?>... annotatedClasses) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses);
return context.getBean(beanClass);
}
@Configuration
@EnableApolloConfig
static class AppConfig1 {
@Bean
public TestApolloConfigBean1 bean() {
return new TestApolloConfigBean1();
}
}
@Configuration
@EnableApolloConfig
static class AppConfig2 {
@Bean
public TestApolloConfigBean2 bean() {
return new TestApolloConfigBean2();
}
}
@Configuration
@EnableApolloConfig
static class AppConfig3 {
@Bean
public TestApolloConfigChangeListenerBean1 bean() {
return new TestApolloConfigChangeListenerBean1();
}
}
@Configuration
@EnableApolloConfig
static class AppConfig4 {
@Bean
public TestApolloConfigChangeListenerBean2 bean() {
return new TestApolloConfigChangeListenerBean2();
}
}
@Configuration
@EnableApolloConfig
static class AppConfig5 {
@Bean
public TestApolloConfigChangeListenerBean3 bean() {
return new TestApolloConfigChangeListenerBean3();
}
}
static class TestApolloConfigBean1 {
@ApolloConfig
private Config config;
@ApolloConfig(ConfigConsts.NAMESPACE_APPLICATION)
private Config anotherConfig;
@ApolloConfig(FX_APOLLO_NAMESPACE)
private Config yetAnotherConfig;
public Config getConfig() {
return config;
}
public Config getAnotherConfig() {
return anotherConfig;
}
public Config getYetAnotherConfig() {
return yetAnotherConfig;
}
}
public static class TestApolloConfigBean2 {
@ApolloConfig
private String config;
}
static class TestApolloConfigChangeListenerBean1 {
private ConfigChangeEvent changeEvent1;
private ConfigChangeEvent changeEvent2;
private ConfigChangeEvent changeEvent3;
@ApolloConfigChangeListener
private void onChange1(ConfigChangeEvent changeEvent) {
this.changeEvent1 = changeEvent;
}
@ApolloConfigChangeListener(ConfigConsts.NAMESPACE_APPLICATION)
private void onChange2(ConfigChangeEvent changeEvent) {
this.changeEvent2 = changeEvent;
}
@ApolloConfigChangeListener({ConfigConsts.NAMESPACE_APPLICATION, FX_APOLLO_NAMESPACE})
private void onChange3(ConfigChangeEvent changeEvent) {
this.changeEvent3 = changeEvent;
}
public ConfigChangeEvent getChangeEvent1() {
return changeEvent1;
}
public ConfigChangeEvent getChangeEvent2() {
return changeEvent2;
}
public ConfigChangeEvent getChangeEvent3() {
return changeEvent3;
}
}
static class TestApolloConfigChangeListenerBean2 {
@ApolloConfigChangeListener
private void onChange(String event) {
}
}
static class TestApolloConfigChangeListenerBean3 {
@ApolloConfigChangeListener
private void onChange(ConfigChangeEvent event, String someParam) {
}
}
}
package com.ctrip.framework.apollo.spring;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
private static final String TIMEOUT_PROPERTY = "timeout";
private static final int DEFAULT_TIMEOUT = 100;
private static final String BATCH_PROPERTY = "batch";
private static final int DEFAULT_BATCH = 200;
private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
@Test
public void testPropertySourceWithNoNamespace() 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);
check(someTimeout, someBatch, AppConfig1.class);
}
@Test
public void testPropertySourceWithNoConfig() throws Exception {
Config config = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
check(DEFAULT_TIMEOUT, DEFAULT_BATCH, AppConfig1.class);
}
@Test
public void testApplicationPropertySource() 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);
check(someTimeout, someBatch, AppConfig2.class);
}
@Test
public void testMultiplePropertySources() throws Exception {
int someTimeout = 1000;
int someBatch = 2000;
Config application = mock(Config.class);
when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
Config fxApollo = mock(Config.class);
when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
check(someTimeout, someBatch, AppConfig3.class);
}
@Test
public void testMultiplePropertySourcesWithSameProperties() throws Exception {
int someTimeout = 1000;
int anotherTimeout = someTimeout + 1;
int someBatch = 2000;
Config application = mock(Config.class);
when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
Config fxApollo = mock(Config.class);
when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout));
mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
check(someTimeout, someBatch, AppConfig3.class);
}
@Test
public void testMultiplePropertySourcesWithSamePropertiesWithWeight() throws Exception {
int someTimeout = 1000;
int anotherTimeout = someTimeout + 1;
int someBatch = 2000;
Config application = mock(Config.class);
when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
Config fxApollo = mock(Config.class);
when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout));
mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
check(anotherTimeout, someBatch, AppConfig2.class, AppConfig4.class);
}
@Test
public void testApplicationPropertySourceWithValueInjectedAsParameter() 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(AppConfig5.class);
TestJavaConfigBean2 bean = context.getBean(TestJavaConfigBean2.class);
assertEquals(someTimeout, bean.getTimeout());
assertEquals(someBatch, bean.getBatch());
}
private void check(int expectedTimeout, int expectedBatch, Class<?>... annotatedClasses) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses);
TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class);
assertEquals(expectedTimeout, bean.getTimeout());
assertEquals(expectedBatch, bean.getBatch());
}
@Configuration
@EnableApolloConfig
static class AppConfig1 {
@Bean
TestJavaConfigBean testJavaConfigBean() {
return new TestJavaConfigBean();
}
}
@Configuration
@EnableApolloConfig("application")
static class AppConfig2 {
@Bean
TestJavaConfigBean testJavaConfigBean() {
return new TestJavaConfigBean();
}
}
@Configuration
@EnableApolloConfig({"application", "FX.apollo"})
static class AppConfig3 {
@Bean
TestJavaConfigBean testJavaConfigBean() {
return new TestJavaConfigBean();
}
}
@Configuration
@EnableApolloConfig(value = "FX.apollo", order = 10)
static class AppConfig4 {
}
@Configuration
@EnableApolloConfig
static class AppConfig5 {
@Bean
TestJavaConfigBean2 testJavaConfigBean2(@Value("${timeout:100}") int timeout, @Value("${batch:200}") int batch) {
TestJavaConfigBean2 bean = new TestJavaConfigBean2();
bean.setTimeout(timeout);
bean.setBatch(batch);
return bean;
}
}
@Component
static class TestJavaConfigBean {
@Value("${timeout:100}")
private int timeout;
private int batch;
@Value("${batch:200}")
public void setBatch(int batch) {
this.batch = batch;
}
public int getTimeout() {
return timeout;
}
public int getBatch() {
return batch;
}
}
static class TestJavaConfigBean2 {
private int timeout;
private int batch;
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getBatch() {
return batch;
}
public void setBatch(int batch) {
this.batch = batch;
}
}
}
package com.ctrip.framework.apollo.spring;
import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.core.ConfigConsts;
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.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class XMLConfigAnnotationTest extends AbstractSpringIntegrationTest {
private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
@Test
public void testApolloConfig() throws Exception {
Config applicationConfig = mock(Config.class);
Config fxApolloConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
mockConfig(FX_APOLLO_NAMESPACE, fxApolloConfig);
TestApolloConfigBean1 bean = getBean("spring/XmlConfigAnnotationTest1.xml", TestApolloConfigBean1.class);
assertEquals(applicationConfig, bean.getConfig());
assertEquals(applicationConfig, bean.getAnotherConfig());
assertEquals(fxApolloConfig, bean.getYetAnotherConfig());
}
@Test(expected = BeanCreationException.class)
public void testApolloConfigWithWrongFieldType() throws Exception {
Config applicationConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
getBean("spring/XmlConfigAnnotationTest2.xml", TestApolloConfigBean2.class);
}
@Test
public void testApolloConfigChangeListener() throws Exception {
Config applicationConfig = mock(Config.class);
Config fxApolloConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
mockConfig(FX_APOLLO_NAMESPACE, fxApolloConfig);
final List<ConfigChangeListener> applicationListeners = Lists.newArrayList();
final List<ConfigChangeListener> fxApolloListeners = Lists.newArrayList();
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
applicationListeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class));
return Void.class;
}
}).when(applicationConfig).addChangeListener(any(ConfigChangeListener.class));
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
fxApolloListeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class));
return Void.class;
}
}).when(fxApolloConfig).addChangeListener(any(ConfigChangeListener.class));
ConfigChangeEvent someEvent = mock(ConfigChangeEvent.class);
ConfigChangeEvent anotherEvent = mock(ConfigChangeEvent.class);
TestApolloConfigChangeListenerBean1 bean = getBean("spring/XmlConfigAnnotationTest3.xml",
TestApolloConfigChangeListenerBean1.class);
assertEquals(3, applicationListeners.size());
assertEquals(1, fxApolloListeners.size());
for (ConfigChangeListener listener : applicationListeners) {
listener.onChange(someEvent);
}
assertEquals(someEvent, bean.getChangeEvent1());
assertEquals(someEvent, bean.getChangeEvent2());
assertEquals(someEvent, bean.getChangeEvent3());
for (ConfigChangeListener listener : fxApolloListeners) {
listener.onChange(anotherEvent);
}
assertEquals(someEvent, bean.getChangeEvent1());
assertEquals(someEvent, bean.getChangeEvent2());
assertEquals(anotherEvent, bean.getChangeEvent3());
}
@Test(expected = BeanCreationException.class)
public void testApolloConfigChangeListenerWithWrongParamType() throws Exception {
Config applicationConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
getBean("spring/XmlConfigAnnotationTest4.xml", TestApolloConfigChangeListenerBean2.class);
}
@Test(expected = BeanCreationException.class)
public void testApolloConfigChangeListenerWithWrongParamCount() throws Exception {
Config applicationConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
getBean("spring/XmlConfigAnnotationTest5.xml", TestApolloConfigChangeListenerBean3.class);
}
private <T> T getBean(String xmlLocation, Class<T> beanClass) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlLocation);
return context.getBean(beanClass);
}
public static class TestApolloConfigBean1 {
@ApolloConfig
private Config config;
@ApolloConfig(ConfigConsts.NAMESPACE_APPLICATION)
private Config anotherConfig;
@ApolloConfig(FX_APOLLO_NAMESPACE)
private Config yetAnotherConfig;
public Config getConfig() {
return config;
}
public Config getAnotherConfig() {
return anotherConfig;
}
public Config getYetAnotherConfig() {
return yetAnotherConfig;
}
}
public static class TestApolloConfigBean2 {
@ApolloConfig
private String config;
}
public static class TestApolloConfigChangeListenerBean1 {
private ConfigChangeEvent changeEvent1;
private ConfigChangeEvent changeEvent2;
private ConfigChangeEvent changeEvent3;
@ApolloConfigChangeListener
private void onChange1(ConfigChangeEvent changeEvent) {
this.changeEvent1 = changeEvent;
}
@ApolloConfigChangeListener(ConfigConsts.NAMESPACE_APPLICATION)
private void onChange2(ConfigChangeEvent changeEvent) {
this.changeEvent2 = changeEvent;
}
@ApolloConfigChangeListener({ConfigConsts.NAMESPACE_APPLICATION, FX_APOLLO_NAMESPACE})
private void onChange3(ConfigChangeEvent changeEvent) {
this.changeEvent3 = changeEvent;
}
public ConfigChangeEvent getChangeEvent1() {
return changeEvent1;
}
public ConfigChangeEvent getChangeEvent2() {
return changeEvent2;
}
public ConfigChangeEvent getChangeEvent3() {
return changeEvent3;
}
}
public static class TestApolloConfigChangeListenerBean2 {
@ApolloConfigChangeListener
private void onChange(String event) {
}
}
public static class TestApolloConfigChangeListenerBean3 {
@ApolloConfigChangeListener
private void onChange(ConfigChangeEvent event, String someParam) {
}
}
}
package com.ctrip.framework.apollo.spring;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.core.ConfigConsts;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class XmlConfigPlaceholderTest extends AbstractSpringIntegrationTest {
private static final String TIMEOUT_PROPERTY = "timeout";
private static final int DEFAULT_TIMEOUT = 100;
private static final String BATCH_PROPERTY = "batch";
private static final int DEFAULT_BATCH = 200;
private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
@Test
public void testPropertySourceWithNoNamespace() 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);
check("spring/XmlConfigPlaceholderTest1.xml", someTimeout, someBatch);
}
@Test
public void testPropertySourceWithNoConfig() throws Exception {
Config config = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
check("spring/XmlConfigPlaceholderTest1.xml", DEFAULT_TIMEOUT, DEFAULT_BATCH);
}
@Test
public void testApplicationPropertySource() 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);
check("spring/XmlConfigPlaceholderTest2.xml", someTimeout, someBatch);
}
@Test
public void testMultiplePropertySources() throws Exception {
int someTimeout = 1000;
int someBatch = 2000;
Config application = mock(Config.class);
when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
Config fxApollo = mock(Config.class);
when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
check("spring/XmlConfigPlaceholderTest3.xml", someTimeout, someBatch);
}
@Test
public void testMultiplePropertySourcesWithSameProperties() throws Exception {
int someTimeout = 1000;
int anotherTimeout = someTimeout + 1;
int someBatch = 2000;
Config application = mock(Config.class);
when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
Config fxApollo = mock(Config.class);
when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout));
mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
check("spring/XmlConfigPlaceholderTest3.xml", someTimeout, someBatch);
}
@Test
public void testMultiplePropertySourcesWithSamePropertiesWithWeight() throws Exception {
int someTimeout = 1000;
int anotherTimeout = someTimeout + 1;
int someBatch = 2000;
Config application = mock(Config.class);
when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
Config fxApollo = mock(Config.class);
when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout));
mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
check("spring/XmlConfigPlaceholderTest4.xml", anotherTimeout, someBatch);
}
@Test(expected = XmlBeanDefinitionStoreException.class)
public void testWithInvalidWeight() throws Exception {
check("spring/XmlConfigPlaceholderTest5.xml", DEFAULT_TIMEOUT, DEFAULT_BATCH);
}
private void check(String xmlLocation, int expectedTimeout, int expectedBatch) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlLocation);
TestXmlBean bean = context.getBean(TestXmlBean.class);
assertEquals(expectedTimeout, bean.getTimeout());
assertEquals(expectedBatch, bean.getBatch());
}
public static class TestXmlBean {
private int timeout;
private int batch;
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getTimeout() {
return timeout;
}
public int getBatch() {
return batch;
}
public void setBatch(int batch) {
this.batch = batch;
}
}
}
<?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.XMLConfigAnnotationTest.TestApolloConfigBean1"/>
</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.XMLConfigAnnotationTest.TestApolloConfigBean2"/>
</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.XMLConfigAnnotationTest.TestApolloConfigChangeListenerBean1"/>
</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.XMLConfigAnnotationTest.TestApolloConfigChangeListenerBean2"/>
</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.XMLConfigAnnotationTest.TestApolloConfigChangeListenerBean3"/>
</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:100}"/>
<property name="batch" value="${batch:200}"/>
</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 namespaces="application"/>
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean">
<property name="timeout" value="${timeout:100}"/>
<property name="batch" value="${batch:200}"/>
</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 namespaces="application,FX.apollo"/>
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean">
<property name="timeout" value="${timeout:100}"/>
<property name="batch" value="${batch:200}"/>
</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 namespaces="application"/>
<apollo:config namespaces="FX.apollo" order="10"/>
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean">
<property name="timeout" value="${timeout:100}"/>
<property name="batch" value="${batch:200}"/>
</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 namespaces="application" order="abc"/>
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean">
<property name="timeout" value="${timeout:100}"/>
<property name="batch" value="${batch:200}"/>
</bean>
</beans>
......@@ -12,13 +12,70 @@
<packaging>jar</packaging>
<properties>
<github.path>${project.artifactId}</github.path>
<!-- apollo spring integration requires Spring 3.1.1+ -->
<spring-demo.version>3.1.1.RELEASE</spring-demo.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-demo.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-demo.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-demo.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-demo.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-demo.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-asm</artifactId>
<version>${spring-demo.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>${project.version}</version>
</dependency>
<!-- for spring demo -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- required for spring 3.1.0 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
......@@ -37,6 +94,4 @@
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
</dependencies>
</project>
package com.ctrip.framework.apollo.demo.api;
import com.google.common.base.Charsets;
import com.ctrip.framework.apollo.Config;
......
package com.ctrip.framework.apollo.demo.api;
import com.google.common.base.Charsets;
import com.ctrip.framework.apollo.Config;
......
package com.ctrip.framework.apollo.demo.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.io.IOException;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class AnnotationApplication {
public static void main(String[] args) {
new AnnotationConfigApplicationContext(AnnotationApplication.class.getPackage().getName());
onKeyExit();
}
private static void onKeyExit() {
System.out.println("Press any key to exit...");
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.ctrip.framework.apollo.demo.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class XmlApplication {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("spring.xml");
onKeyExit();
}
private static void onKeyExit() {
System.out.println("Press any key to exit...");
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.ctrip.framework.apollo.demo.spring.bean;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.model.ConfigChange;
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.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Component
public class AnnotatedBean {
private static final Logger logger = LoggerFactory.getLogger(AnnotatedBean.class);
@Value("${timeout:200}")
private int timeout;
private int batch;
@ApolloConfig
private Config config;
@ApolloConfig("FX.apollo")
private Config anotherConfig;
@PostConstruct
void initialize() {
logger.info("timeout is {}", timeout);
logger.info("batch is {}", batch);
logger.info("Keys for config: {}", config.getPropertyNames());
logger.info("Keys for anotherConfig: {}", anotherConfig.getPropertyNames());
}
@Value("${batch:100}")
public void setBatch(int batch) {
this.batch = batch;
}
@ApolloConfigChangeListener("application")
private void someChangeHandler(ConfigChangeEvent changeEvent) {
logger.info("[someChangeHandler]Changes for namespace {}", changeEvent.getNamespace());
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
logger.info("[someChangeHandler]Change - key: {}, oldValue: {}, newValue: {}, changeType: {}",
change.getPropertyName(), change.getOldValue(), change.getNewValue(),
change.getChangeType());
}
}
@ApolloConfigChangeListener({"application", "FX.apollo"})
private void anotherChangeHandler(ConfigChangeEvent changeEvent) {
logger.info("[anotherChangeHandler]Changes for namespace {}", changeEvent.getNamespace());
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
logger.info("[anotherChangeHandler]Change - key: {}, oldValue: {}, newValue: {}, changeType: {}",
change.getPropertyName(), change.getOldValue(), change.getNewValue(),
change.getChangeType());
}
}
}
package com.ctrip.framework.apollo.demo.spring.bean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.PostConstruct;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class NormalBean {
private static final Logger logger = LoggerFactory.getLogger(NormalBean.class);
@Value("${timeout:200}")
private int timeout;
private int batch;
@PostConstruct
void initialize() {
logger.info("timeout is {}", timeout);
logger.info("batch is {}", batch);
}
public void setBatch(int batch) {
this.batch = batch;
}
}
package com.ctrip.framework.apollo.demo.spring.bean;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.model.ConfigChange;
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.InitializingBean;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class XmlBean implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(XmlBean.class);
private int timeout;
private int batch;
@ApolloConfig
private Config config;
@ApolloConfig("FX.apollo")
private Config anotherConfig;
public void setTimeout(int timeout) {
this.timeout = timeout;
logger.info("Setting timeout to {}", timeout);
}
public void setBatch(int batch) {
this.batch = batch;
logger.info("Setting batch to {}", batch);
}
@Override
public void afterPropertiesSet() throws Exception {
logger.info("Keys for config: {}", config.getPropertyNames());
logger.info("Keys for anotherConfig: {}", anotherConfig.getPropertyNames());
}
@ApolloConfigChangeListener("application")
private void someChangeHandler(ConfigChangeEvent changeEvent) {
logger.info("[someChangeHandler]Changes for namespace {}", changeEvent.getNamespace());
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
logger.info("[someChangeHandler]Change - key: {}, oldValue: {}, newValue: {}, changeType: {}",
change.getPropertyName(), change.getOldValue(), change.getNewValue(),
change.getChangeType());
}
}
@ApolloConfigChangeListener({"application", "FX.apollo"})
private void anotherChangeHandler(ConfigChangeEvent changeEvent) {
logger.info("[anotherChangeHandler]Changes for namespace {}", changeEvent.getNamespace());
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
logger.info("[anotherChangeHandler]Change - key: {}, oldValue: {}, newValue: {}, changeType: {}",
change.getPropertyName(), change.getOldValue(), change.getNewValue(),
change.getChangeType());
}
}
}
package com.ctrip.framework.apollo.demo.spring.config;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.springframework.context.annotation.Configuration;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Configuration
@EnableApolloConfig(value = "FX.apollo", order = 11)
public class AnotherAppConfig {
}
package com.ctrip.framework.apollo.demo.spring.config;
import com.ctrip.framework.apollo.demo.spring.bean.NormalBean;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Configuration
@EnableApolloConfig(value = "application", order = 10)
public class AppConfig {
@Bean
public NormalBean normalBean(@Value("${batch:100}") int batch) {
NormalBean bean = new NormalBean();
bean.setBatch(batch);
return bean;
}
}
......@@ -10,6 +10,9 @@
</appenders>
<loggers>
<logger name="com.ctrip.framework.apollo" additivity="false" level="trace">
<AppenderRef ref="Async" level="INFO"/>
</logger>
<logger name="com.ctrip.framework.apollo.demo" additivity="false" level="trace">
<AppenderRef ref="Async" level="DEBUG"/>
</logger>
<root level="INFO">
......
<?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 order="10"/>
<apollo:config namespaces="FX.apollo" order="11"/>
<bean class="com.ctrip.framework.apollo.demo.spring.bean.XmlBean">
<property name="timeout" value="${timeout:200}"/>
<property name="batch" value="${batch:100}"/>
</bean>
</beans>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册