Commit cae2215f by nobodyiam

1. Refactor SpringApplicationRunListener and move it to apollo-client

2. Reorganize the feature to provide the ability to inject apollo configs in Spring Boot bootstrap phase 3. Add more integration tests to cover different scenarios 4. Update apollo-demo to show this apollo bootstrap config feature could be used with @ConditionalOnProperty
parent 87dd185f
## Apollo Spring Boot Starter ##
### 一、说明 ###
由于@EnableApolloConfig使用ImportBeanDefinitionRegistrar初始化PropertySource,时机晚于spring boot的其他PropertySource,使得@ConditionalOnProperty无法生效。这里使用SpringApplicationRunListener初始化Apollo的PropertySource,优先级如下所示:
```text
StubPropertySource {name='servletConfigInitParams'}
StubPropertySource {name='servletContextInitParams'}
MapPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
CompositePropertySource [name='ApolloPropertySources', propertySources=[ConfigPropertySource {name='application'}]]
RandomValuePropertySource {name='random'}
PropertiesPropertySource {name='applicationConfig: [classpath:/application-local.properties]'}
PropertiesPropertySource {name='applicationConfig: [classpath:/application.properties]'}
MapPropertySource {name='refresh'}
```
### 二、Maven Dependency ###
```xml
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-spring-boot-starter</artifactId>
<version>0.9.1</version>
</dependency>
<!-- 由于apollo-client通常是自行打包发布的,这里的版本不确定,所以需要自行引入 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>0.9.1-SNAPSHOT</version>
</dependency>
```
### 三、配置 ###
```properties
#默认为true
apollo.enabled=true
#默认为application
apollo.namespaces=application,FX.apollo
```
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-spring-boot-starter</artifactId>
<name>Apollo Spring Boot Starter</name>
<version>0.9.1</version>
<properties>
<java.version>1.7</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.boot.version>1.5.9.RELEASE</spring.boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- apollo client -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>0.9.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
package com.ctrip.framework.apollo.boot;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.env.EnumerableCompositePropertySource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.*;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class ApolloSpringApplicationRunListener implements SpringApplicationRunListener, PriorityOrdered {
private static final String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
private static Logger logger = LoggerFactory.getLogger(ApolloSpringApplicationRunListener.class);
private SpringApplication application;
private String[] args;
public ApolloSpringApplicationRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
}
public void starting() {
}
public void environmentPrepared(ConfigurableEnvironment environment) {
}
public void contextPrepared(ConfigurableApplicationContext context) {
}
public void contextLoaded(ConfigurableApplicationContext configurableApplicationContext) {
ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
String enabled = environment.getProperty("apollo.enabled", "true");
if (!"true".equals(enabled)) {
logger.warn("Apollo is not enabled. see property: ${apollo.enabled}");
return;
}
String namespaces = environment.getProperty("apollo.namespaces", "application");
logger.info("Configured namespaces: {}", namespaces);
List<String> namespaceList = Arrays.asList(namespaces.split(","));
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources.contains(APOLLO_PROPERTY_SOURCE_NAME)) {
//already initialized
return;
}
CompositePropertySource composite = new CompositePropertySource(APOLLO_PROPERTY_SOURCE_NAME);
for (String namespace : namespaceList) {
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(new ConfigPropertySource(namespace, config));
}
propertySources.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, composite);
StringBuilder logInfo = new StringBuilder();
for (PropertySource<?> propertySource : propertySources) {
if (propertySource.getClass().getSimpleName().contains("ConfigurationPropertySources")) {
//打印文件配置
try {
Field field = propertySource.getClass().getDeclaredField("sources");
field.setAccessible(true);
List list = (List) ReflectionUtils.getField(field, propertySource);
for (Object s : list) {
if (s instanceof EnumerableCompositePropertySource) {
EnumerableCompositePropertySource enumerableCompositePropertySource = (EnumerableCompositePropertySource) s;
Collection<PropertySource<?>> source = enumerableCompositePropertySource.getSource();
for (PropertySource<?> a : source) {
logInfo.append('\t').append(a.toString()).append("\n");
}
}
}
} catch (NoSuchFieldException e) {
//do nothing
}
} else {
logInfo.append('\t').append(propertySource.toString()).append("\n");
}
}
logger.info("PropertySources piority:\n{}", logInfo.toString());
}
public void finished(ConfigurableApplicationContext configurableApplicationContext, Throwable throwable) {
}
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
package com.ctrip.framework.apollo.boot;
import com.ctrip.framework.apollo.boot.bean.TestBean;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ApolloTestConfigurationWithConditionalOnProperty.class})
public class ApolloBootStarterWithConditionalOnPropertyTests {
@BeforeClass
public static void beforeClass() {
System.setProperty("app.id", "1"); // C:\opt\data\1\config-cache\1+default+application.properties文件内容:apollo.test.testBean=true
System.setProperty("spring.profiles.active", "local");
System.setProperty("env", "local");
}
@Autowired
private TestBean testBean;
@Test
public void testSuccess() {
Assert.notNull(testBean, "testBean is not null");
Assert.isTrue(testBean.execute(), "testBean.execute success");
}
}
package com.ctrip.framework.apollo.boot;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import com.ctrip.framework.apollo.boot.bean.TestBean;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ApolloTestConfigurationWithConditionalOnProperty.class})
public class ApolloBootStarterWithConditionalOnPropertyTests_Failed {
@BeforeClass
public static void beforeClass() {
// System.setProperty("app.id", "1"); app.id not set
System.setProperty("spring.profiles.active", "local");
System.setProperty("env", "local");
}
@Autowired(required = false)
private TestBean testBean;
@Test
public void testWithAppIdNotSet() {
Assert.isNull(testBean, "testBean is not null");
}
}
package com.ctrip.framework.apollo.boot;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import com.ctrip.framework.apollo.boot.bean.TestBean;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ApolloTestConfigurationWithoutConditionalOnProperty.class})
public class ApolloBootStarterWithoutConditionalOnPropertyTests {
@BeforeClass
public static void beforeClass() {
System.setProperty("app.id", "1"); // C:\opt\data\1\config-cache\1+default+application.properties文件内容:apollo.test.testBean=true
System.setProperty("spring.profiles.active", "local");
System.setProperty("env", "local");
}
@Autowired
private TestBean testBean;
@Test
public void testSuccess() {
Assert.notNull(testBean, "testBean is not null");
Assert.isTrue(testBean.execute(), "testBean.execute success");
}
}
package com.ctrip.framework.apollo.boot;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import com.ctrip.framework.apollo.boot.bean.TestBean;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ApolloTestConfigurationWithoutConditionalOnProperty.class})
public class ApolloBootStarterWithoutConditionalOnPropertyTests_WithoutAppId {
@BeforeClass
public static void beforeClass() {
// System.setProperty("app.id", "1"); app id not set
System.setProperty("spring.profiles.active", "local");
System.setProperty("env", "local");
}
@Autowired
private TestBean testBean;
@Test
public void testSuccess() {
Assert.notNull(testBean, "testBean is not null");
Assert.isTrue(testBean.execute(), "testBean.execute success");
}
}
package com.ctrip.framework.apollo.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
@SpringBootConfiguration
public class ApolloTestApplication {
public static void main(String[] args) {
SpringApplication.run(ApolloTestApplication.class, args);
}
}
package com.ctrip.framework.apollo.boot;
import com.ctrip.framework.apollo.boot.bean.TestBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApolloTestConfigurationWithConditionalOnProperty {
@Bean
@ConditionalOnProperty(prefix = "apollo.test", name = "testBean")
public TestBean testBean() {
return new TestBean();
}
}
package com.ctrip.framework.apollo.boot;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ctrip.framework.apollo.boot.bean.TestBean;
@Configuration
public class ApolloTestConfigurationWithoutConditionalOnProperty {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
package com.ctrip.framework.apollo.boot.bean;
public class TestBean {
public boolean execute() {
return true;
}
}
...@@ -38,6 +38,12 @@ ...@@ -38,6 +38,12 @@
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- optional spring boot dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<!-- test --> <!-- test -->
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
......
package com.ctrip.framework.apollo.boot; package com.ctrip.framework.apollo.spring.boot;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySource;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourcesProcessor; import com.ctrip.framework.apollo.spring.config.ConfigPropertySourcesProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants;
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
@ConditionalOnClass(ConfigPropertySource.class) @ConditionalOnProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED)
@ConditionalOnProperty(prefix = "apollo", name = "enabled", matchIfMissing = true) @ConditionalOnMissingBean(PropertySourcesProcessor.class)
public class ApolloAutoConfiguration { public class ApolloAutoConfiguration {
@Bean @Bean
......
package com.ctrip.framework.apollo.spring.boot;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySource;
import com.google.common.base.Splitter;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* Inject the Apollo config in Spring Boot bootstrap phase
*
* <p>Configuration example:</p>
* <pre class="code">
* # will inject 'application' namespace in bootstrap phase
* apollo.bootstrap.enabled = true
* </pre>
*
* or
*
* <pre class="code">
* apollo.bootstrap.enabled = true
* # will inject 'application' and 'TEST1.apollo' namespaces in bootstrap phase
* apollo.bootstrap.namespaces = application,FX.apollo
* </pre>
*/
public class ApolloSpringApplicationRunListener implements SpringApplicationRunListener,
PriorityOrdered {
private static final Logger logger = LoggerFactory.getLogger(ApolloSpringApplicationRunListener.class);
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
public ApolloSpringApplicationRunListener(SpringApplication application, String[] args) {
//ignore
}
public void starting() {
}
public void started() {
}
public void environmentPrepared(ConfigurableEnvironment environment) {
}
public void contextPrepared(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
String enabled = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "false");
if (!Boolean.valueOf(enabled)) {
logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
return;
}
logger.debug("Apollo bootstrap config is enabled for context {}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
//already initialized
return;
}
String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
logger.debug("Apollo bootstrap namespaces: {}", namespaces);
List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
for (String namespace : namespaceList) {
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(new ConfigPropertySource(namespace, config));
}
environment.getPropertySources().addFirst(composite);
}
public void contextLoaded(ConfigurableApplicationContext context) {
}
public void finished(ConfigurableApplicationContext configurableApplicationContext,
Throwable throwable) {
}
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
package com.ctrip.framework.apollo.spring.config;
public interface PropertySourcesConstants {
String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
String APOLLO_BOOTSTRAP_ENABLED = "apollo.bootstrap.enabled";
String APOLLO_BOOTSTRAP_NAMESPACES = "apollo.bootstrap.namespaces";
}
...@@ -31,7 +31,6 @@ import java.util.Iterator; ...@@ -31,7 +31,6 @@ import java.util.Iterator;
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered { public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
private static final String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
private static final Multimap<Integer, String> NAMESPACE_NAMES = HashMultimap.create(); private static final Multimap<Integer, String> NAMESPACE_NAMES = HashMultimap.create();
private ConfigurableEnvironment environment; private ConfigurableEnvironment environment;
...@@ -46,11 +45,11 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir ...@@ -46,11 +45,11 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
} }
protected void initializePropertySources() { protected void initializePropertySources() {
if (environment.getPropertySources().contains(APOLLO_PROPERTY_SOURCE_NAME)) { if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
//already initialized //already initialized
return; return;
} }
CompositePropertySource composite = new CompositePropertySource(APOLLO_PROPERTY_SOURCE_NAME); CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);
//sort by order asc //sort by order asc
ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet()); ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());
...@@ -64,8 +63,16 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir ...@@ -64,8 +63,16 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
composite.addPropertySource(new ConfigPropertySource(namespace, config)); composite.addPropertySource(new ConfigPropertySource(namespace, config));
} }
} }
// add after the bootstrap property source or to the first
if (environment.getPropertySources()
.contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
environment.getPropertySources()
.addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);
} else {
environment.getPropertySources().addFirst(composite); environment.getPropertySources().addFirst(composite);
} }
}
@Override @Override
public void setEnvironment(Environment environment) { public void setEnvironment(Environment environment) {
......
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ctrip.framework.apollo.boot.ApolloAutoConfiguration com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration
org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.SpringApplicationRunListener=\
com.ctrip.framework.apollo.boot.ApolloSpringApplicationRunListener com.ctrip.framework.apollo.spring.boot.ApolloSpringApplicationRunListener
\ No newline at end of file
package com.ctrip.framework.apollo; package com.ctrip.framework.apollo;
import com.ctrip.framework.apollo.spring.BootstrapConfigTest;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Suite; import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses; import org.junit.runners.Suite.SuiteClasses;
...@@ -35,7 +36,7 @@ import com.ctrip.framework.apollo.util.parser.DurationParserTest; ...@@ -35,7 +36,7 @@ import com.ctrip.framework.apollo.util.parser.DurationParserTest;
ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class, PropertiesConfigFileTest.class, ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class, PropertiesConfigFileTest.class,
RemoteConfigLongPollServiceTest.class, DateParserTest.class, DurationParserTest.class, JsonConfigFileTest.class, RemoteConfigLongPollServiceTest.class, DateParserTest.class, DurationParserTest.class, JsonConfigFileTest.class,
XmlConfigPlaceholderTest.class, JavaConfigPlaceholderTest.class, XMLConfigAnnotationTest.class, XmlConfigPlaceholderTest.class, JavaConfigPlaceholderTest.class, XMLConfigAnnotationTest.class,
JavaConfigAnnotationTest.class, ConfigUtilTest.class JavaConfigAnnotationTest.class, ConfigUtilTest.class, BootstrapConfigTest.class
}) })
public class AllTests { public class AllTests {
......
...@@ -37,6 +37,19 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -37,6 +37,19 @@ public abstract class AbstractSpringIntegrationTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
doSetUp();
}
@After
public void tearDown() throws Exception {
doTearDown();
}
protected static void mockConfig(String namespace, Config config) {
CONFIG_REGISTRY.put(namespace, config);
}
protected static void doSetUp() {
//as PropertySourcesProcessor has some static states, so we must manually clear its state //as PropertySourcesProcessor has some static states, so we must manually clear its state
ReflectionUtils.invokeMethod(PROPERTY_SOURCES_PROCESSOR_CLEAR, null); ReflectionUtils.invokeMethod(PROPERTY_SOURCES_PROCESSOR_CLEAR, null);
//as ConfigService is singleton, so we must manually clear its container //as ConfigService is singleton, so we must manually clear its container
...@@ -45,15 +58,10 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -45,15 +58,10 @@ public abstract class AbstractSpringIntegrationTest {
MockInjector.setInstance(ConfigManager.class, new MockConfigManager()); MockInjector.setInstance(ConfigManager.class, new MockConfigManager());
} }
@After protected static void doTearDown() {
public void tearDown() throws Exception {
CONFIG_REGISTRY.clear(); CONFIG_REGISTRY.clear();
} }
protected void mockConfig(String namespace, Config config) {
CONFIG_REGISTRY.put(namespace, config);
}
public static class MockConfigManager implements ConfigManager { public static class MockConfigManager implements ConfigManager {
@Override @Override
......
package com.ctrip.framework.apollo.spring;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants;
import com.google.common.collect.Sets;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(Enclosed.class)
public class BootstrapConfigTest {
private static final String TEST_BEAN_CONDITIONAL_ON_KEY = "apollo.test.testBean";
private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(ConfigurationWithConditionalOnProperty.class)
@DirtiesContext
public static class TestWithBootstrapEnabledAndDefaultNamespacesAndConditionalOn extends
AbstractSpringIntegrationTest {
private static final String someProperty = "someProperty";
private static final String someValue = "someValue";
@Autowired(required = false)
private TestBean testBean;
@ApolloConfig
private Config config;
@Value("${" + someProperty + "}")
private String someInjectedValue;
private static Config mockedConfig;
@BeforeClass
public static void beforeClass() throws Exception {
doSetUp();
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "true");
mockedConfig = mock(Config.class);
when(mockedConfig.getPropertyNames()).thenReturn(Sets.newHashSet(TEST_BEAN_CONDITIONAL_ON_KEY, someProperty));
when(mockedConfig.getProperty(eq(TEST_BEAN_CONDITIONAL_ON_KEY), anyString())).thenReturn(Boolean.TRUE.toString());
when(mockedConfig.getProperty(eq(someProperty), anyString())).thenReturn(someValue);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mockedConfig);
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
doTearDown();
}
@Test
public void test() throws Exception {
Assert.assertNotNull(testBean);
Assert.assertTrue(testBean.execute());
Assert.assertEquals(mockedConfig, config);
Assert.assertEquals(someValue, someInjectedValue);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(ConfigurationWithConditionalOnProperty.class)
@DirtiesContext
public static class TestWithBootstrapEnabledAndNamespacesAndConditionalOn extends
AbstractSpringIntegrationTest {
@Autowired(required = false)
private TestBean testBean;
@BeforeClass
public static void beforeClass() throws Exception {
doSetUp();
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "true");
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES,
String.format("%s, %s", ConfigConsts.NAMESPACE_APPLICATION, FX_APOLLO_NAMESPACE));
Config config = mock(Config.class);
Config anotherConfig = mock(Config.class);
when(config.getPropertyNames()).thenReturn(Sets.newHashSet(TEST_BEAN_CONDITIONAL_ON_KEY));
when(config.getProperty(eq(TEST_BEAN_CONDITIONAL_ON_KEY), anyString())).thenReturn(Boolean.TRUE.toString());
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, anotherConfig);
mockConfig(FX_APOLLO_NAMESPACE, config);
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES);
doTearDown();
}
@Test
public void test() throws Exception {
Assert.assertNotNull(testBean);
Assert.assertTrue(testBean.execute());
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(ConfigurationWithConditionalOnProperty.class)
@DirtiesContext
public static class TestWithBootstrapEnabledAndDefaultNamespacesAndConditionalOnFailed extends
AbstractSpringIntegrationTest {
@Autowired(required = false)
private TestBean testBean;
@BeforeClass
public static void beforeClass() throws Exception {
doSetUp();
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "true");
Config config = mock(Config.class);
when(config.getPropertyNames()).thenReturn(Sets.newHashSet(TEST_BEAN_CONDITIONAL_ON_KEY));
when(config.getProperty(eq(TEST_BEAN_CONDITIONAL_ON_KEY), anyString())).thenReturn(Boolean.FALSE.toString());
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
doTearDown();
}
@Test
public void test() throws Exception {
Assert.assertNull(testBean);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(ConfigurationWithoutConditionalOnProperty.class)
@DirtiesContext
public static class TestWithBootstrapEnabledAndDefaultNamespacesAndConditionalOff extends
AbstractSpringIntegrationTest {
@Autowired(required = false)
private TestBean testBean;
@BeforeClass
public static void beforeClass() throws Exception {
doSetUp();
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "true");
Config config = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
doTearDown();
}
@Test
public void test() throws Exception {
Assert.assertNotNull(testBean);
Assert.assertTrue(testBean.execute());
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(ConfigurationWithConditionalOnProperty.class)
@DirtiesContext
public static class TestWithBootstrapDisabledAndDefaultNamespacesAndConditionalOn extends
AbstractSpringIntegrationTest {
@Autowired(required = false)
private TestBean testBean;
@BeforeClass
public static void beforeClass() throws Exception {
doSetUp();
Config config = mock(Config.class);
when(config.getPropertyNames()).thenReturn(Sets.newHashSet(TEST_BEAN_CONDITIONAL_ON_KEY));
when(config.getProperty(eq(TEST_BEAN_CONDITIONAL_ON_KEY), anyString())).thenReturn(Boolean.FALSE.toString());
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
}
@AfterClass
public static void afterClass() throws Exception {
doTearDown();
}
@Test
public void test() throws Exception {
Assert.assertNull(testBean);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(ConfigurationWithoutConditionalOnProperty.class)
@DirtiesContext
public static class TestWithBootstrapDisabledAndDefaultNamespacesAndConditionalOff extends
AbstractSpringIntegrationTest {
@Autowired(required = false)
private TestBean testBean;
@BeforeClass
public static void beforeClass() throws Exception {
doSetUp();
Config config = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
doTearDown();
}
@Test
public void test() throws Exception {
Assert.assertNotNull(testBean);
Assert.assertTrue(testBean.execute());
}
}
@EnableAutoConfiguration
@Configuration
static class ConfigurationWithoutConditionalOnProperty {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
@ConditionalOnProperty(TEST_BEAN_CONDITIONAL_ON_KEY)
@EnableAutoConfiguration
@Configuration
static class ConfigurationWithConditionalOnProperty {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
static class TestBean {
public boolean execute() {
return true;
}
}
}
...@@ -6,6 +6,7 @@ import java.util.List; ...@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -15,6 +16,7 @@ import javax.annotation.PostConstruct; ...@@ -15,6 +16,7 @@ import javax.annotation.PostConstruct;
/** /**
* You may set up data like the following in Apollo: * You may set up data like the following in Apollo:
* <pre> * <pre>
* redis.cache.enabled = true
* redis.cache.expireSeconds = 100 * redis.cache.expireSeconds = 100
* redis.cache.clusterNodes = 1,2 * redis.cache.clusterNodes = 1,2
* redis.cache.commandTimeout = 50 * redis.cache.commandTimeout = 50
...@@ -24,8 +26,14 @@ import javax.annotation.PostConstruct; ...@@ -24,8 +26,14 @@ import javax.annotation.PostConstruct;
* redis.cache.someList[1] = d * redis.cache.someList[1] = d
* </pre> * </pre>
* *
* To make <code>@ConditionalOnProperty</code> work properly, <code>apollo.bootstrap.enabled</code> should be set to true
* and <code>redis.cache.enabled</code> should also be set to true.
*
* @see resources/bootstrap.yml
*
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
@ConditionalOnProperty("redis.cache.enabled")
@ConfigurationProperties(prefix = "redis.cache") @ConfigurationProperties(prefix = "redis.cache")
@Component("sampleRedisConfig") @Component("sampleRedisConfig")
@RefreshScope @RefreshScope
......
...@@ -8,12 +8,14 @@ import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; ...@@ -8,12 +8,14 @@ import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
@ConditionalOnProperty("redis.cache.enabled")
@Component @Component
public class SpringBootApolloRefreshConfig { public class SpringBootApolloRefreshConfig {
private static final Logger logger = LoggerFactory.getLogger(SpringBootApolloRefreshConfig.class); private static final Logger logger = LoggerFactory.getLogger(SpringBootApolloRefreshConfig.class);
......
apollo:
bootstrap:
enabled: true
# will inject 'application' and 'FX.apollo' namespaces in bootstrap phase
namespaces: application,FX.apollo
...@@ -101,7 +101,6 @@ ...@@ -101,7 +101,6 @@
<module>apollo-portal</module> <module>apollo-portal</module>
<module>apollo-assembly</module> <module>apollo-assembly</module>
<module>apollo-demo</module> <module>apollo-demo</module>
<module>apollo-boot-starter</module>
</modules> </modules>
<dependencyManagement> <dependencyManagement>
......
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