Apollo 公共 Namespace 使用

2019-05-29  本文已影响0人  _晓__

背景

现在使用 Apollo 配置中心框架的公司越来越多了,也希望写这篇文章对刚入手 Apollo 的同学有所帮助,对系统做出更多更好用的功能。

问题举例

所需知识

Apollo Java客户端使用指南

问题解决

私有 Namespace 和 公共 Namespace 区别

更好的使用公共 Namespace

由于 Apollo 默认只是把 application 加入 Spring PropertySources 中,会导致 Spring 初始化 mysql,redis 等组件时,无法获取到公共 Namespace 中的配置信息,所以 Apollo 提供了配置方式 apollo.bootstrap.enabled=true apollo.bootstrap.namespaces=redis,mysql(多个使用逗号隔开),把公共 Namespace 加入 Spring PropertySources 中,但这种方式有个弊端就是公共 Namespace 需人工配置加载,若再新增一个公共 Namespace,还需修改 apollo.bootstrap.namespaces(对加载顺序不了解,可看 ApolloApplicationContextInitializer),因此,我自己实现获取公共 Namespace 加入 Spring PropertySources 的过程并支持公共 Namespace 配置的自动更新,无需配置 @EnableApolloConfig,还支持 @ConfigurationProperties 配置类自动更新。

import cn.hutool.core.text.StrFormatter;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory;
import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants;
import com.ctrip.framework.apollo.spring.util.SpringInjector;
import com.ctrip.framework.apollo.util.http.HttpRequest;
import com.ctrip.framework.apollo.util.http.HttpResponse;
import com.ctrip.framework.apollo.util.http.HttpUtil;
import com.ctrip.framework.foundation.internals.provider.DefaultApplicationProvider;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gson.reflect.TypeToken;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 需在resources目录下新增META-INF/spring.factories文件,文件内容为:
 * org.springframework.boot.env.EnvironmentPostProcessor=\
 * 包名.ApolloContextInitializer
 * org.springframework.context.ApplicationContextInitializer=\
 * 包名.ApolloContextInitializer
 * 
 * 对 EnvironmentPostProcessor 和 ApplicationContextInitializer 不清楚的,可自行百度了解
 */
@Slf4j
public class ApolloContextInitializer implements EnvironmentPostProcessor, ApplicationContextInitializer<ConfigurableApplicationContext> {
    
    /**
     * 配置是否执行
     */
    private final static boolean APOLLO_ENABLE = Boolean.valueOf(System.getProperty(ApolloConfigConsts.APOLLO_ENABLE, Boolean.TRUE.toString()));

    private final static Set<Config> CONFIGS = Sets.newHashSet();

    static {
        if (APOLLO_ENABLE) {
            // 检查重复配置 spring.factories
            ApolloContextInitializer.checkDuplicate(EnvironmentPostProcessor.class);
        }
    }

    private static void checkDuplicate(Class<EnvironmentPostProcessor> factoryClass) {
        try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION));
            List<String> apolloContextInitializerList = Lists.newArrayListWithCapacity(5);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                String urlStr = url.toString();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : org.springframework.util.StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        factoryName = factoryName.trim();
                        if (factoryClassName.equals(factoryClass.getName()) && factoryName.equals(ApolloContextInitializer.class.getName())) {
                            apolloContextInitializerList.add(StrFormatter.format("{}#{}", urlStr, factoryName));
                        }
                    }
                }
            }
            Assert.isTrue(apolloContextInitializerList.size() <= 1,
                StrFormatter.format("项目中发现有{}个{}都配置了{},重复配置文件路径=>{},请检查lib目录下是否存在重复配置",
                        apolloContextInitializerList.size(), SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION,
                        ApolloContextInitializer.class.getName(), apolloContextInitializerList.stream().collect(Collectors.joining(",")))
            );
        } catch (IOException e) {
            throw new IllegalArgumentException(StrFormatter.format("加载{}文件异常,请检查是否存在该配置文件", SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION), e);
        }
    }

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        initialize(environment);
    }

    private void initialize(ConfigurableEnvironment environment) {
        if (!APOLLO_ENABLE) {
            return;
        }

        List<ApolloConfig> apolloConfigList = allApolloConfig();
        Assert.notEmpty(apolloConfigList, StrFormatter.format("该项目没有Apollo配置项,如无需使用Apollo,请在VM参数中配置-D{}=false", ApolloConfigConsts.APOLLO_ENABLE));
        CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
        ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);
        for (ApolloConfig apolloConfig : apolloConfigList) {
            Config config = ConfigService.getConfig(apolloConfig.getNamespaceName());
            composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(apolloConfig.getNamespaceName(), config));
            CONFIGS.add(config);
        }
        environment.getPropertySources().addFirst(composite);
    }

    private List<ApolloConfig> allApolloConfig() {
        DefaultApplicationProvider defaultApplicationProvider = getApplicationProvider();
        String configServiceUri = getConfigServiceUri(defaultApplicationProvider);
        ServiceDTO adminService = getAdminService(configServiceUri);
        return allApolloConfig(defaultApplicationProvider, adminService);
    }

    private List<ApolloConfig> allApolloConfig(DefaultApplicationProvider defaultApplicationProvider, ServiceDTO adminService) {
        String clustersName = System.getProperty(ConfigConsts.APOLLO_CLUSTER_KEY, ConfigConsts.CLUSTER_NAME_DEFAULT);
        String namespacesUrl = StrFormatter.format(ApolloConfigConsts.NAMESPACES_URL_PATTERN, adminService.getHomepageUrl(), defaultApplicationProvider.getAppId(), clustersName);
        try {
            HttpUtil httpUtil = new HttpUtil();
            HttpRequest request = new HttpRequest(namespacesUrl);
            request.setConnectTimeout(ApolloConfigConsts.TIMEOUT);
            request.setReadTimeout(ApolloConfigConsts.TIMEOUT);
            HttpResponse<List<ApolloConfig>> response = httpUtil.doGet(request, new TypeToken<List<ApolloConfig>>() {}.getType());
            return response.getBody();
        } catch (Exception e) {
            throw new IllegalStateException(StrFormatter.format("获取Apollo信息异常,请求url=>{},请检查配置", namespacesUrl), e);
        }
    }

    private DefaultApplicationProvider getApplicationProvider() {
        DefaultApplicationProvider defaultApplicationProvider = new DefaultApplicationProvider();
        defaultApplicationProvider.initialize();
        return defaultApplicationProvider;
    }

    private ServiceDTO getAdminService(String metaServiceUri) {
        String adminServiceUrl = StrFormatter.format(ApolloConfigConsts.ADMIN_SERVICE_URL_PATTERN, metaServiceUri);
        try {
            HttpUtil httpUtil = new HttpUtil();
            HttpRequest request = new HttpRequest(adminServiceUrl);
            request.setConnectTimeout(ApolloConfigConsts.TIMEOUT);
            request.setReadTimeout(ApolloConfigConsts.TIMEOUT);
            HttpResponse<List<ServiceDTO>> response = httpUtil.doGet(request, new TypeToken<List<ServiceDTO>>() {}.getType());
            List<ServiceDTO> adminServiceList = response.getBody();
            Assert.notEmpty(adminServiceList, StrFormatter.format("无Apollo AdminService服务实例,请检查服务,如无需使用Apollo,请在VM参数中配置-D{}=false", ApolloConfigConsts.APOLLO_ENABLE));
            return adminServiceList.get(0);
        } catch (Exception e) {
            throw new IllegalStateException(StrFormatter.format("获取Apollo AdminService服务实例信息异常,请求url=>{},请检查配置,如无需使用Apollo,请在VM参数中配置-D{}=false", adminServiceUrl, ApolloConfigConsts.APOLLO_ENABLE), e);
        }
    }

    private String getConfigServiceUri(DefaultApplicationProvider defaultApplicationProvider) {
        String configServiceUri = System.getProperty(ConfigConsts.APOLLO_META_KEY);
        if (StringUtils.isBlank(configServiceUri)) {
            configServiceUri = defaultApplicationProvider.getProperty(ConfigConsts.APOLLO_META_KEY, StringUtils.EMPTY);
        }
        Assert.hasText(configServiceUri, StrFormatter.format("Apollo ConfigService uri未配置,如无需使用Apollo,请在VM参数中配置-D{}=false", ApolloConfigConsts.APOLLO_ENABLE));
        configServiceUri = configServiceUri.indexOf(",") > 0 ? configServiceUri.split(",")[0] : configServiceUri;
        return configServiceUri;
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        if (CollectionUtils.isEmpty(CONFIGS)) {
            return;
        }
        // 创建 Apollo 监听器
        ConfigRefreshListener configRefreshListener = new ConfigRefreshListener(applicationContext);
        // 所有 Namespace Config 对象添加监听器
        CONFIGS.stream().forEach(config -> config.addChangeListener(configRefreshListener));
    }
}

@AllArgsConstructor
class ConfigRefreshListener implements ConfigChangeListener {

    private ApplicationContext applicationContext;

    @Override
    public void onChange(ConfigChangeEvent changeEvent) {
        // 使用 Spring Cloud EnvironmentChangeEvent 刷新配置
        applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
    }
}
public interface ApolloConfigConsts {

  int TIMEOUT = 3000;
  String APOLLO_ENABLE = "apollo.enable";
  String ADMIN_SERVICE_URL_PATTERN = "{}services/admin";
  String NAMESPACES_URL_PATTERN = "{}apps/{}/clusters/{}/namespaces";
}
PS:
对代码不明白地方可联系讨论或对写的不好的地方望不吝指教。
上一篇下一篇

猜你喜欢

热点阅读