配置中心在现在的分布式系统可以说是一个必不可少的核心组件了,国内使用最广泛的配置中心应该是携程开发的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,更新配置的值。