Commit 87dd185f by 胡昇 Committed by nobodyiam

Spring Boot中使用SpringApplicationRunListener初始化Apollo的PropertySource

parent 39dc2979
## 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.spring.config.ConfigPropertySource;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourcesProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass(ConfigPropertySource.class)
@ConditionalOnProperty(prefix = "apollo", name = "enabled", matchIfMissing = true)
public class ApolloAutoConfiguration {
@Bean
public ConfigPropertySourcesProcessor configPropertySourcesProcessor() {
return new ConfigPropertySourcesProcessor();
}
}
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;
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ctrip.framework.apollo.boot.ApolloAutoConfiguration
org.springframework.boot.SpringApplicationRunListener=\
com.ctrip.framework.apollo.boot.ApolloSpringApplicationRunListener
\ No newline at end of file
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;
}
}
...@@ -101,6 +101,7 @@ ...@@ -101,6 +101,7 @@
<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