Apollo客戶端的配置主要集中在2部分,這就致使割裂了配置,十分不便:java
在spring-boot環境下,該類中,經過對配置項apollo.bootstrap.enabled的開啓,來達到Apollo在spring-boot環境下的autoconfig,其中主要是initialize方法中,對於指定配置,從spring的properties中加載到system.property裏去,由於Apollo自己全部配置都是支持system.property的,因此經過這麼實現,能夠對原有的配置加載體系沒有侵入。
關鍵代碼以下(部分無關代碼已省略):git
public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> , EnvironmentPostProcessor, Ordered { //對應的會被從spring.properties中轉移到system.property中的參數名 private static final String[] APOLLO_SYSTEM_PROPERTIES = {"app.id", ConfigConsts.APOLLO_CLUSTER_KEY, "apollo.cacheDir", "apollo.accesskey.secret", ConfigConsts.APOLLO_META_KEY, PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE}; //實現spring提供的接口,裏面進行配置的轉移 @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); } } //能夠看到只操做了final static數組裏定義的那個列表 void initializeSystemProperty(ConfigurableEnvironment environment) { for (String propertyName : APOLLO_SYSTEM_PROPERTIES) { fillSystemPropertyFromEnvironment(environment, propertyName); } } //直接把存在的參數加到系統參數中去,有點暴力 private void fillSystemPropertyFromEnvironment(ConfigurableEnvironment environment, String propertyName) { if (System.getProperty(propertyName) != null) { return; } String propertyValue = environment.getProperty(propertyName); if (Strings.isNullOrEmpty(propertyValue)) { return; } System.setProperty(propertyName, propertyValue); }
另外一部分參數,即env以及apollo.configService這兩個主要參數,經過DefaultProviderManager類中對DefaultServerProvider的加載實現,這兩個類都是Apollo自定義的類,主要功能僅支持從系統變量,jvm啓動參數,指定文件目錄讀取這兩個配置。
主要實現代碼:github
public class DefaultServerProvider implements ServerProvider { private static final Logger logger = LoggerFactory.getLogger(DefaultServerProvider.class); private static final String SERVER_PROPERTIES_LINUX = "/opt/settings/server.properties"; private static final String SERVER_PROPERTIES_WINDOWS = "C:/opt/settings/server.properties"; private String m_env; private String m_dc; private Properties m_serverProperties = new Properties(); //能夠看到主要就是從2個固定的目錄去讀一個特定的文件,若是讀不到,後續就是從系統變量和jvm取了 @Override public void initialize() { try { String path = Utils.isOSWindows() ? SERVER_PROPERTIES_WINDOWS : SERVER_PROPERTIES_LINUX; File file = new File(path); if (file.exists() && file.canRead()) { logger.info("Loading {}", file.getAbsolutePath()); FileInputStream fis = new FileInputStream(file); initialize(fis); return; } initialize(null); } catch (Throwable ex) { logger.error("Initialize DefaultServerProvider failed.", ex); } } //獲取參數時的處理 private void initEnvType() { // 1. Try to get environment from JVM system property m_env = System.getProperty("env"); if (!Utils.isBlank(m_env)) { m_env = m_env.trim(); logger.info("Environment is set to [{}] by JVM system property 'env'.", m_env); return; } // 2. Try to get environment from OS environment variable m_env = System.getenv("ENV"); if (!Utils.isBlank(m_env)) { m_env = m_env.trim(); logger.info("Environment is set to [{}] by OS env variable 'ENV'.", m_env); return; } // 3. Try to get environment from file "server.properties" m_env = m_serverProperties.getProperty("env"); if (!Utils.isBlank(m_env)) { m_env = m_env.trim(); logger.info("Environment is set to [{}] by property 'env' in server.properties.", m_env); return; } // 4. Set environment to null. m_env = null; logger.info("Environment is set to null. Because it is not available in either (1) JVM system property 'env', (2) OS env variable 'ENV' nor (3) property 'env' from the properties InputStream."); } }
直接上代碼,這裏攔截使用了bytebuddyspring
<dependencies> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.10.22</version> </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy-agent</artifactId> <version>1.10.22</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.16.RELEASE</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> <configuration> <artifactSet> <includes> <include>net.bytebuddy:byte-buddy:jar:</include> <include>net.bytebuddy:byte-buddy-agent:jar:</include> </includes> </artifactSet> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <configuration> <archive> <manifestEntries> <Premain-Class>cn.intotw.springconfig.apollo.AgentMain</Premain-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
//攔截主函數 public class AgentMain { public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException { System.out.println("進入premain"); System.out.println("isRedefineClassesSupported: " + inst.isRedefineClassesSupported()); new AgentBuilder.Default() .type(ElementMatchers.named("com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer")) .transform((builder, type, loader, module) -> builder .method(named("postProcessEnvironment")) .intercept(MethodDelegation.to(SpringConfigInterceptor.class))) .installOn(inst); } } //攔截器 public class SpringConfigInterceptor { private static final String[] APOLLO_SYSTEM_PROPERTIES_APPEND = {"env","apollo.configService"}; @RuntimeType public static void intercept(@Argument(0) Object args1, @SuperCall Callable<?> callable) throws Exception { try { initializeSystemProperty((ConfigurableEnvironment) args1); callable.call(); } finally { System.out.println("after advice"); } } static void initializeSystemProperty(ConfigurableEnvironment environment) { for (String propertyName : APOLLO_SYSTEM_PROPERTIES_APPEND) { fillSystemPropertyFromEnvironment(environment, propertyName); } } static void fillSystemPropertyFromEnvironment(ConfigurableEnvironment environment, String propertyName) { if (System.getProperty(propertyName) != null) { return; } String propertyValue = environment.getProperty(propertyName); if (propertyValue==null ||"".equals(propertyValue)) { return; } System.setProperty(propertyName, propertyValue); } }
其實主要就是對該函數的加載進行攔截,複製一段代碼增長一下對自定義參數的讀取,原理如出一轍。
代碼放到github上了[https://github.com/IntoTw/apollo-springconfig-support]apache
翻源碼的時候發現,Apollo對於DefaultProviderManager提供了SPI支持,因此也能夠直接經過實現com.ctrip.framework.foundation.spi.ProviderManager接口,來直接用本身的配置加載實現替代原有實現。
bootstrap
SPI部分代碼:數組
public class MyProviderManager implements ProviderManager { @Override public String getProperty(String name, String defaultValue) { return null; } @Override public <T extends Provider> T provider(Class<T> clazz) { return null; } }
debug,能夠看到此時加載的已是咱們實現的本身的manager了。
app