通常在spring项目中,我们是通过扫描resources下的配置文件,将配置文件中的配置解析出来,在容器启动时,通过invokeBeanFactoryPostProcessors来替换占位符的。
同理,当我们需要接入配置中心时,理论上,原理不变,我们也是在执行invokeBeanFactoryPostProcessors方法时来替换占位符,我们需要解决的是,将之前通过扫描配置文件解析出来配置,替换成读取zk,将其中的配置放到对应的地方,供替换时使用
在xml文件中,创建PreferencesPlaceholderConfigurer的bean,其properties属性引用properties加载类,这个加载类即为加载zk配置的自定义类
<bean id="prop" class="com.dome.config.ZkPropFactoryBean"></bean>
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="properties" ref="prop"/>
</bean>
容器初始化时,会加载xml,我们上面定义的bean会被加载成beanDefinition放到beanFactory中,在执行invokeBeanFactoryPostProcessors方法时,会从beanFactory中筛选各种BeanFactoryPostProcessor来执行,而我们定义的propertyConfigurer这个bean,他的类路径PreferencesPlaceholderConfigurer即为实现了BeanFactoryPostProcessor和PriorityOrdered的,所以同样会被筛选出来
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
..此处省略大部分无关代码..
List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>();
for (String ppName : postProcessorNames) {
if (processedBeans.contains(ppName)) {
}
else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
//我们定义的propertyConfigurer会在这里被筛选出来,筛选出来就提前实例化这个bean,
//而且,我们自定义的propertyConfigurer这个bean,他的属性引用的是prop这个bean,所以这两个bean会在这一步一起初始化出来
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
//这里就是替换占位符了,这里后面说完初始化在说
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
..此处省略大部分无关代码..
}
getBean这个方法太复杂了,就抽一点重点代码出来
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
..此处省略大部分无关代码..
// Initialize the bean instance.
Object exposedObject = bean;
try {
//在实例化bean的时候,这里会填充bean的属性,我们定义的prop是propertyConfigurer的属性,所以在创建propertyConfigurer的时候,会先实例化prop
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
..此处省略大部分无关代码..
}
在初始化bean的时候,会将zk的bean也初始化
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.core.io.Resource;
public class ZkPropFactoryBean extends PropertiesFactoryBean implements FactoryBean<Properties>, InitializingBean {
private Properties properties;
public void setLocations(Resource... locations) {
//这里就会初始化加载zk了
ZookeeperPropertiesLoader.instance
}
//因为改bean继承了PropertiesFactoryBean所以实例化bean的时候还会走这里
protected Properties createProperties() throws IOException {
//将 ZkPropLoader.instance.getProperties() 获取到的zk配置,全部放到properties 中,
//这样的话ZkPropFactoryBean在实例化完成后其properties属性中,放置的全部都是zk的配置
}
}
这里是定义一个枚举,用来加载zk配置中心的配置
public enum ZkPropLoader {
instance;
//在这里连接zk,将其中的配置都放到properties中
private Map<String, String> properties = new LinkedHashMap();
}
根据上面bean初始化流程,我们知道
1、容器先初始化propertyConfigurer,因为其中属性引用prop
2、实例化prop,在这里过程中,链接zk,并且将配置全都放到其中的properties中
3、上面的prop实例化结束后,prop会走factory.getObject()
@Override
@Nullable
public final Properties getObject() throws IOException {
if (this.singleton) {
//我们在刚才实例化时知道singleton是true而且singletonInstance会加载properties
return this.singletonInstance;
}
else {
return createProperties();
}
}
4、所以propertyConfigurer加载完成后其properties属性也会是zk加载的内容
上面是bean初始化,zk初始化,的一系列动作,算是一个准备工作
接下来就是invokeBeanFactoryPostProcessors这个方法的invokeBeanFactoryPostProcessors了,这时候才正式开始解析占位符
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
//加载上面的Properties,即得到的zk配置
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// 这里会吧beanfactory中的beanDefinition拿出来,解析每一个的占位符
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file locations.
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
//在这里
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}
}
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
if (beanDefinition.hasPropertyValues()) {
//这里是看你的beanDefinition中有没有属性值
visitPropertyValues(beanDefinition.getPropertyValues());
}
if (beanDefinition.hasConstructorArgumentValues()) {
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
}
@Nullable
protected Object resolveValue(@Nullable Object value) {
//占位符会在这里
else if (value instanceof TypedStringValue) {
TypedStringValue typedStringValue = (TypedStringValue) value;
String stringValue = typedStringValue.getValue();
if (stringValue != null) {
String visitedString = resolveStringValue(stringValue);
typedStringValue.setValue(visitedString);
}
}
return value;
}
@Nullable
protected String resolveStringValue(String strVal) {
if (this.valueResolver == null) {
throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
"object into the constructor or override the 'resolveStringValue' method");
}
String resolvedValue = this.valueResolver.resolveStringValue(strVal);
// Return original String if not modified.
return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}
@Override
@Nullable
public String resolveStringValue(String strVal) throws BeansException {
String resolved = this.helper.replacePlaceholders(strVal, this.resolver);
if (trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(nullValue) ? null : resolved);
}
后面就是一点一点跟代码了
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (visitedPlaceholders == null) {
visitedPlaceholders = new HashSet<>(4);
}
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
//这里就会在properties中取出我们的配置
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
//当我们的配置不为空,则接下来就是把beanDefinition中的属性占位符替换一下
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
上面忽略了很多过程,代码太多了,但是基本的流程有了,上面就已经吧所有的占位符替换完了,这样在后面实例化对应的bean时,就会使用zk中获取到的值了