diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java index 908792b30d9..3959b1ef962 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java @@ -11,6 +11,8 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.CompositePropertySource; @@ -37,9 +39,22 @@ * # will inject 'application' and 'FX.apollo' namespaces in bootstrap phase * apollo.bootstrap.namespaces = application,FX.apollo * + * + * + * If you want to load Apollo configurations even before Logging System Initialization Phase, + * add + *
+ *   # set apollo.bootstrap.eagerLoad.enabled
+ *   apollo.bootstrap.eagerLoad.enabled = true
+ * 
+ * + * This would be very helpful when your logging configurations is set by Apollo. + * + * for example, you have defined logback-spring.xml in your project, and you want to inject some attributes into logback-spring.xml. + * */ public class ApolloApplicationContextInitializer implements - ApplicationContextInitializer { + ApplicationContextInitializer , EnvironmentPostProcessor { private static final Logger logger = LoggerFactory.getLogger(ApolloApplicationContextInitializer.class); private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults(); private static final String[] APOLLO_SYSTEM_PROPERTIES = {"app.id", ConfigConsts.APOLLO_CLUSTER_KEY, @@ -52,8 +67,6 @@ public class ApolloApplicationContextInitializer implements public void initialize(ConfigurableApplicationContext context) { ConfigurableEnvironment environment = context.getEnvironment(); - initializeSystemProperty(environment); - 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); @@ -61,6 +74,17 @@ public void initialize(ConfigurableApplicationContext context) { } logger.debug("Apollo bootstrap config is enabled for context {}", context); + initialize(environment); + } + + + /** + * Initialize Apollo Configurations Just after environment is ready. + * + * @param environment + */ + protected void initialize(ConfigurableEnvironment environment) { + if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) { //already initialized return; @@ -102,4 +126,37 @@ private void fillSystemPropertyFromEnvironment(ConfigurableEnvironment environme System.setProperty(propertyName, propertyValue); } + + /** + * + * In order to load Apollo configurations as early as even before Spring loading logging system phase, + * this EnvironmentPostProcessor can be called Just After ConfigFileApplicationListener has succeeded. + * + *
+ * The processing sequence would be like this:
+ * Load Bootstrap properties and application properties -----> load Apollo configuration properties ----> Initialize Logging systems + * + * @param configurableEnvironment + * @param springApplication + */ + @Override + public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) { + + // should always initialize system properties like app.id in the first place + initializeSystemProperty(configurableEnvironment); + + Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false); + + //EnvironmentPostProcessor should not be triggered if you don't want Apollo Loading before Logging System Initialization + if (!eagerLoadEnabled) { + return; + } + + Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false); + + if (bootstrapEnabled) { + initialize(configurableEnvironment); + } + + } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesConstants.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesConstants.java index 37b75d28098..b402e06dfea 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesConstants.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesConstants.java @@ -4,5 +4,6 @@ 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_EAGER_LOAD_ENABLED = "apollo.bootstrap.eagerLoad.enabled"; String APOLLO_BOOTSTRAP_NAMESPACES = "apollo.bootstrap.namespaces"; } diff --git a/apollo-client/src/main/resources/META-INF/spring.factories b/apollo-client/src/main/resources/META-INF/spring.factories index a4b7944a8d1..80c3b509bd3 100644 --- a/apollo-client/src/main/resources/META-INF/spring.factories +++ b/apollo-client/src/main/resources/META-INF/spring.factories @@ -2,3 +2,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration org.springframework.context.ApplicationContextInitializer=\ com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer +org.springframework.boot.env.EnvironmentPostProcessor=\ +com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/BootstrapConfigTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/BootstrapConfigTest.java index 13961a1959a..d223c3df229 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/BootstrapConfigTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/BootstrapConfigTest.java @@ -1,14 +1,12 @@ 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.boot.ApolloApplicationContextInitializer; import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; import com.google.common.collect.Sets; import org.junit.AfterClass; import org.junit.Assert; @@ -20,12 +18,21 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import java.util.List; + +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) */ @@ -265,6 +272,48 @@ public void test() throws Exception { } } + + @RunWith(SpringJUnit4ClassRunner.class) + @SpringBootTest(classes = ConfigurationWithoutConditionalOnProperty.class) + @DirtiesContext + public static class TestWithBootstrapEnabledAndEagerLoadEnabled extends + AbstractSpringIntegrationTest { + + @BeforeClass + public static void beforeClass() { + doSetUp(); + + System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "true"); + System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, "true"); + + Config config = mock(Config.class); + + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); + } + + @AfterClass + public static void afterClass() { + System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED); + System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED); + + doTearDown(); + } + + @Test + public void test() { + List processorList = SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader()); + + Boolean containsApollo = !Collections2.filter(processorList, new Predicate() { + @Override + public boolean apply(EnvironmentPostProcessor input) { + return input instanceof ApolloApplicationContextInitializer; + } + }).isEmpty(); + Assert.assertTrue(containsApollo); + } + } + + @EnableAutoConfiguration @Configuration static class ConfigurationWithoutConditionalOnProperty {