改造apollo配置中心客戶端支持從Spring讀取配置

主要問題

Apollo客戶端的配置主要集中在2部分,這就致使割裂了配置,十分不便:java

ApolloApplicationContextInitializer

在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);
  }

DefaultProviderManager

另外一部分參數,即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.");
  }
}

改造思路

java-agent對ApolloApplicationContextInitializer.postProcessEnvironment進行攔截

直接上代碼,這裏攔截使用了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的SPI支持,實現本身的DefaultProviderManager來替換

翻源碼的時候發現,Apollo對於DefaultProviderManager提供了SPI支持,因此也能夠直接經過實現com.ctrip.framework.foundation.spi.ProviderManager接口,來直接用本身的配置加載實現替代原有實現。
bootstrap

  1. 在resources文件下新建META-INF/services/com.ctrip.framework.foundation.spi.ProviderManager文件。
  2. 文件內容爲本身實現的接口,代碼隨便抄抄源碼而後改一下就好了。

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

相關文章
相關標籤/搜索