配置中心在现在的分布式系统可以说是一个必不可少的核心组件了,国内使用最广泛的配置中心应该是携程开发的Apollo。Apollo的思想还是挺简单的,简单地说,服务端利用Eureka做高可用,配置信息存到数据库,客户端调用服务端的接口,拉取最新的配置,如果有变化,就更新客户端的配置。服务端提供了可视化的图形界面去修改配置,保存到数据库。我们今天先来看下客户端部分是如何来实现的。

客户端的主要功能就是调用服务端接口,获取最新的配置,然后更新配置。由于apollo-client的代码还是比较复杂了,因此在它的基础上删减了一版simple-apollo-client,代码非常少,非常容易学习。

客户端

(1)添加依赖

<dependency>
  <groupId>com.github.xjs</groupId>
  <artifactId>simple-apollo-client</artifactId>
</dependency>

(2)添加注解

@EnableApolloConfig
@SpringBootApplication
public class ApolloApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApolloApplication.class, args);
    }
}

(3)添加配置

// resources/META-INF/app.properties
appId=app1
meta=http://localhost:8000

看下测试代码:

@RestController
public class DemoController {
    @Value("${user.username:Joshua}")
    private String username;
    @Value("${user.password:abcdefg}")
    private String password;
    @Value("${user.notexist:notexist}")
    private String notExist;
    @Autowired
    private DemoProperties properties;
    @GetMapping("/hello")
    public String hello(){
        String str = "@Value:" + username+","+password+","+notExist;
        str += "-------------";
        str += "@ConfigurationProperties:" + properties.getUsername() + "," + properties.getPassword();
        return str;
    }
}

用法跟正版的apollo是一模一样的。

当然要想把demo跑起来,还得有个apollo的服务端。

服务端

@RestController
public class ConfigController {
    /**模拟数据库*/
    private Map<String, Map<String, String>> configs = new HashMap<String, Map<String, String>>();

    @PostConstruct
    public void init(){
        Map<String, String> map = new HashMap<>();
        map.put("user.username", "Joshua");
        map.put("user.password", "123456");
        configs.put("app1", map);
    }

    /**用于客户端查询*/
    @GetMapping("/get_config")
    public ApolloConfig getConfig(String appid){
        Map<String, String> map = configs.get(appid);
        return new ApolloConfig(appid, map);
    }

    /**用户服务端更新配置*/
    @GetMapping("/update_config")
    public String updateConfig(String appid, String key, String value){
        Map<String, String> map = configs.get(appid);
        if(map != null){
            map.put(key, value);
        }
        return "ok";
    }
}

simple-apollo-client

从EnableApolloConfig开始:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ApolloConfigRegistrar.class)
public @interface EnableApolloConfig {
}

ApolloConfigRegistrar:

public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
                PropertySourcesPlaceholderConfigurer.class, null);
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
                PropertySourcesProcessor.class);
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(),
                SpringValueProcessor.class);
    }
}

这里就是注册了3个bean definition:PropertySourcesPlaceholderConfigurer、PropertySourcesProcessor、SpringValueProcessor。其中PropertySourcesPlaceholderConfigurer是Spring原生就提供的。

PropertySourcesProcessor:

public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {

    public static final String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";

    private ConfigurableEnvironment environment;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        initializePropertySources(beanFactory);
    }

    private void initializePropertySources(ConfigurableListableBeanFactory beanFactory) {
        if (environment.getPropertySources().contains(APOLLO_PROPERTY_SOURCE_NAME)) {
            return;
        }
        //添加PropertySource
        CompositePropertySource composite = new CompositePropertySource(APOLLO_PROPERTY_SOURCE_NAME);
        DefaultConfig config = DefaultConfig.getConfig();
        ConfigPropertySource configPropertySource = new ConfigPropertySource("defaultConfigPropertySource",config);
        composite.addPropertySource(configPropertySource);
        environment.getPropertySources().addFirst(composite);
        // 给PropertySource添加监听,实际上就是给config添加监听
        ConfigChangeListener configChangeListener = new ConfigChangeListener(beanFactory);
        configPropertySource.addChangeListener(configChangeListener);
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = (ConfigurableEnvironment) environment;
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

}

PropertySourcesProcessor实现了BeanFactoryPostProcessor,向environment中添加一个CompositePropertySource。

CompositePropertySource里面有一个ConfigPropertySource,ConfigPropertySource里面有一个DefaultConfig,DefaultConfig里面有一个RemoteConfigRepository,RemoteConfigRepository负责从服务端拉配置,因此客户端可以注入远程的配置。

DefaultConfig实现了RepositoryChangeListener,当RemoteConfigRepository检测到有变化的时候,会回调DefaultConfig,DefaultConfig会继续回调ConfigChangeListener,在这个ConfigChangeListener里面会去做真正的配置值的更新。

那应该更新哪些值呢?

SpringValueProcessor:

public class SpringValueProcessor implements BeanPostProcessor {

    private SpringValueRegistry springValueRegistry;
    private PlaceholderHelper placeholderHelper;

    public SpringValueProcessor(){
        this.springValueRegistry = SpringValueRegistry.getInstance();
        this.placeholderHelper = PlaceholderHelper.getInstance();
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        Class clazz = bean.getClass();
        for (Field field : findAllField(clazz)) {
            processField(bean, beanName, field);
        }
        return bean;
    }

    protected void processField(Object bean, String beanName, Field field) {
        // register @Value on field
        Value value = field.getAnnotation(Value.class);
        if (value == null) {
            return;
        }
        Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
        if (keys.isEmpty()) {
            return;
        }
        for (String key : keys) {
            SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field);
            springValueRegistry.register(key, springValue);
        }
    }

    private List<Field> findAllField(Class clazz) {
        final List<Field> res = new LinkedList<>();
        ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                res.add(field);
            }
        });
        return res;
    }
}

实际就是扫描系统中所有的@Value字段,转化成SpringValue,保存到SpringValueRegistry中。

SpringValue:

public class SpringValue {
    private Field field;
    private Object bean;
    private String beanName;
    private String key;
    private String placeholder;
    private Class<?> targetType;
}

SpringValue里面就记录了field,bean,key,targetType,因此当有配置更新的时候,就是去SpringValueRegistry中读取要更新的key,用反射重置它的value。

整体的流程大概就是这个样子了,总结一下:

(1)SpringValueProcessor解析系统中所有的@Value,保存到SpringValueRegistry。

(2)PropertySourcesProcessor不断调用服务端接口,拉取最新的配置,计算变化,根据SpringValueProcessor,更新配置的值。