0、xml配置命名空间规则
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
http://www.ctrip.com/schema/apollo
http://www.ctrip.com/schema/apollo.xsd">
spring xml解析beans时会找到对应spring.handlers 拓展NamespaceHandler
http://www.ctrip.com/schema/apollo=com.ctrip.framework.apollo.spring.config.NamespaceHandler
1、根据xml中的配置,spring解析道apollo命名空间后调用NamespaceHandler 重写的init方法
<apollo:config/>
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class NamespaceHandler extends NamespaceHandlerSupport {
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
@Override
public void init() {
registerBeanDefinitionParser("config", new BeanParser());
}
static class BeanParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return ConfigPropertySourcesProcessor.class;
}
@Override
protected boolean shouldGenerateId() {
return true;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String namespaces = element.getAttribute("namespaces");
//default to application
if (Strings.isNullOrEmpty(namespaces)) {
namespaces = ConfigConsts.NAMESPACE_APPLICATION;
}
int order = Ordered.LOWEST_PRECEDENCE;
String orderAttribute = element.getAttribute("order");
if (!Strings.isNullOrEmpty(orderAttribute)) {
try {
order = Integer.parseInt(orderAttribute);
} catch (Throwable ex) {
throw new IllegalArgumentException(
String.format("Invalid order: %s for namespaces: %s", orderAttribute, namespaces));
}
}
//加入到静态变量中存储起来
PropertySourcesProcessor.addNamespaces(NAMESPACE_SPLITTER.splitToList(namespaces), order);
}
}
}
2、handler 记载命名空间到PropertySourcesProcessor静态变量中,存储的内容就是在阿波罗管理后台定义的 application等命名空间。
3、PropertySourcesProcessor 作为beanfacotry postprocessor postProcessBeanFactory方法被调用。
4、Apollo在Spring ApplicationContext Enviroment PropertySources 存储的key = “ApolloPropertySources”,判断是否初始化过这个内容。
5、创建一个 CompositePropertySource 自定义配置源,名叫“ApolloPropertySources”。
/**
* Apollo Property Sources processor for Spring Annotation Based Application
*
* @author Jason Song(song_s@ctrip.com)
*/
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
private static final String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
private static final Multimap<Integer, String> NAMESPACE_NAMES = HashMultimap.create();
private ConfigurableEnvironment environment;
public static boolean addNamespaces(Collection<String> namespaces, int order) {
return NAMESPACE_NAMES.putAll(order, namespaces);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
initializePropertySources();
}
protected void initializePropertySources() {
//判断environment中是不是有叫做ApolloPropertySources的配置源
if (environment.getPropertySources().contains(APOLLO_PROPERTY_SOURCE_NAME)) {
//already initialized
return;
}
CompositePropertySource composite = new CompositePropertySource(APOLLO_PROPERTY_SOURCE_NAME);
//sort by order asc
ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());
Iterator<Integer> iterator = orders.iterator();
while (iterator.hasNext()) {
int order = iterator.next();
for (String namespace : NAMESPACE_NAMES.get(order)) {
//这里会获取对应命名空间“application”对应的Config实现类实例
Config config = ConfigService.getConfig(namespace);
//多个namespace放到组合配置源中
composite.addPropertySource(new ConfigPropertySource(namespace, config));
}
}
//组合数据源放到environment数据源池子中
environment.getPropertySources().addFirst(composite);
}
@Override
public void setEnvironment(Environment environment) {
//it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
this.environment = (ConfigurableEnvironment) environment;
}
//only for test
private static void reset() {
NAMESPACE_NAMES.clear();
}
@Override
public int getOrder() {
//make it as early as possible
return Ordered.HIGHEST_PRECEDENCE;
}
}
6、遍历命名空间去获取对应的配置,这时候刚启动这些配置是空的,脱离spring new了一个ConfigService 开启管理。
/**
* Entry point for client config use
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigService {
//new 一个ConfigService 配置服务类
private static final ConfigService s_instance = new ConfigService();
private PlexusContainer m_container;
private volatile ConfigManager m_configManager;
private volatile ConfigRegistry m_configRegistry;
private ConfigService() {
m_container = ContainerLoader.getDefaultContainer();
}
//获取或创建一个管理器 下面单例多重判断,以及m_configManager 这只为volatile!
private ConfigManager getManager() {
if (m_configManager == null) {
synchronized (this) {
if (m_configManager == null) {
try {
m_configManager = m_container.lookup(ConfigManager.class);
} catch (ComponentLookupException ex) {
ApolloConfigException exception = new ApolloConfigException("Unable to load ConfigManager!", ex);
Tracer.logError(exception);
throw exception;
}
}
}
}
return m_configManager;
}
/**
* Get Application's config instance.
*
* @return config instance
*/
public static Config getAppConfig() {
return getConfig(ConfigConsts.NAMESPACE_APPLICATION);
}
/**
* Get the config instance for the namespace.
*
* @param namespace the namespace of the config
* @return config instance
*/
public static Config getConfig(String namespace) {
return s_instance.getManager().getConfig(namespace);
}
7、DefaultConfigFactory 会开始创建一个namespace对应的config对象
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Named(type = ConfigFactory.class)
public class DefaultConfigFactory implements ConfigFactory {
private static final Logger logger = LoggerFactory.getLogger(DefaultConfigFactory.class);
@Inject
private ConfigUtil m_configUtil;
@Override
public Config create(String namespace) {
DefaultConfig defaultConfig =
new DefaultConfig(namespace, createLocalConfigRepository(namespace));
return defaultConfig;
}
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
ConfigRepository configRepository = createLocalConfigRepository(namespace);
switch (configFileFormat) {
case Properties:
return new PropertiesConfigFile(namespace, configRepository);
case XML:
return new XmlConfigFile(namespace, configRepository);
case JSON:
return new JsonConfigFile(namespace, configRepository);
case YAML:
return new YamlConfigFile(namespace, configRepository);
case YML:
return new YmlConfigFile(namespace, configRepository);
}
return null;
}
LocalFileConfigRepository createLocalConfigRepository(String namespace) {
if (m_configUtil.isInLocalMode()) {
logger.warn(
"==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====",
namespace);
return new LocalFileConfigRepository(namespace);
}
//或创建一个本地文件配置仓库类,带命名空间以及生成一个远程配置仓库类-同步远程配置。
return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
}
RemoteConfigRepository createRemoteConfigRepository(String namespace) {
return new RemoteConfigRepository(namespace);
}
}
8、获取对应namespace的Config对象时会在LocalFileConfigRepository 中传入一个RemoteConfigRepository
RemoteConfigRepository 创建时会尝试连接Apollo服务端
/**
* Constructor.
*
* @param namespace the namespace
*/
public RemoteConfigRepository(String namespace) {
m_namespace = namespace;
m_configCache = new AtomicReference<>();
m_container = ContainerLoader.getDefaultContainer();
try {
m_configUtil = m_container.lookup(ConfigUtil.class);
m_httpUtil = m_container.lookup(HttpUtil.class);
m_serviceLocator = m_container.lookup(ConfigServiceLocator.class);
remoteConfigLongPollService = m_container.lookup(RemoteConfigLongPollService.class);
} catch (ComponentLookupException ex) {
Tracer.logError(ex);
throw new ApolloConfigException("Unable to load component!", ex);
}
m_longPollServiceDto = new AtomicReference<>();
m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
//尝试同步远程
this.trySync();
//定时器同步刷新
this.schedulePeriodicRefresh();
this.scheduleLongPollingRefresh();
}
9、本地文件配置仓库初始化,传入一个远程仓库对象
public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
m_namespace = namespace;
m_container = ContainerLoader.getDefaultContainer();
try {
m_configUtil = m_container.lookup(ConfigUtil.class);
} catch (ComponentLookupException ex) {
Tracer.logError(ex);
throw new ApolloConfigException("Unable to load component!", ex);
}
this.setLocalCacheDir(findLocalCacheDir(), false);
//设置上游仓库,也就是仓库,这里面又个巧妙设计是有个监听器
this.setUpstreamRepository(upstream);
//同步本地
this.trySync();
}
10、将本地文件配置源自己作为监听器注册到上游(远程)配置仓库对象中
@Override
public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
if (upstreamConfigRepository == null) {
return;
}
//clear previous listener
if (m_upstream != null) {
m_upstream.removeChangeListener(this);
}
m_upstream = upstreamConfigRepository;
trySyncFromUpstream();
upstreamConfigRepository.addChangeListener(this);
}
//远程同步到变更是处罚本地文件更新
@Override
public void onRepositoryChange(String namespace, Properties newProperties) {
if (newProperties.equals(m_fileProperties)) {
return;
}
Properties newFileProperties = new Properties();
newFileProperties.putAll(newProperties);
//更新当前本地文件,更新文件会被定时加载到本地对象变量中
updateFileProperties(newFileProperties);
//监听器传播下去
this.fireRepositoryChange(namespace, newProperties);
}
11、至此,environment中的apollo propertysources就设置好了
12、准备好配置定时同步以及加载到environment中存储的对象,接下来就是如何在spring 装配时将配置对象和具体引用的对象关联起来;如下,ApolloAnnotationProcessor来解析,这是一个beanpostprocessor,当bean初始化前后来处理bean。下面用到了反射,直接将bean对应的带有@ApolloConfig("application")的属性设置值为对应命名空间的config配置仓库对象。
/**
* Apollo Annotation Processor for Spring Application
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ApolloAnnotationProcessor implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class clazz = bean.getClass();
processFields(bean, clazz.getDeclaredFields());
processMethods(bean, clazz.getDeclaredMethods());
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private void processFields(Object bean, Field[] declaredFields) {
for (Field field : declaredFields) {
ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
if (annotation == null) {
continue;
}
Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()),
"Invalid type: %s for field: %s, should be Config", field.getType(), field);
//重点在这里
String namespace = annotation.value();
Config config = ConfigService.getConfig(namespace);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, bean, config);
}
}
private void processMethods(final Object bean, Method[] declaredMethods) {
for (final Method method : declaredMethods) {
ApolloConfigChangeListener annotation = AnnotationUtils.findAnnotation(method, ApolloConfigChangeListener.class);
if (annotation == null) {
continue;
}
Class<?>[] parameterTypes = method.getParameterTypes();
Preconditions.checkArgument(parameterTypes.length == 1,
"Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, method);
Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
"Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], method);
ReflectionUtils.makeAccessible(method);
String[] namespaces = annotation.value();
for (String namespace : namespaces) {
Config config = ConfigService.getConfig(namespace);
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
});
}
}
}
@Override
public int getOrder() {
//make it as late as possible
return Ordered.LOWEST_PRECEDENCE;
}
}
12、经过上面的初始化操作,就完成了对象装配,业务上就可以调用获取参数配置,并且由底层仓库对象去更新配置了,业务直接用就行。