Spring IOC源码之(1)prepareRefresh

工作中一直和Spring框架打交道,可是从来都没有真正去了解过该它。只知道Spring框架给项目带来极大的便利,降低了代码之间的耦合度,而不至于牵一发而动全身,还提供了非常丰富的扩展点、高级特性等等。其中最为重要是IOC、AOP。接下来抽出一段时间来记录阅读框架的点点滴滴

为了更好的梳理整条线大致的流转,用一张图贯穿始终,方便自己的记住Spring的大致脉络。先确定始终,始:包含Bean的定义信息配置文件,终:Bean定义信息封装在BeanDefinition中,并保存在DefaultListableBeanFactory容器中。

通过ClassPathXmlApplicationContext(版本4.3.10.RELEASE)作为源码的入口。如下是最简单启动方式。

// Car类中只有一个属性name,还包含set、get、构造方法、toString方法
@Test
public void ClassPathXmlApplicationContext() {
	ClassPathXmlApplicationContext context =
			new ClassPathXmlApplicationContext("conf/SpringIOC.xml");
	Car car = context.getBean(Car.class);
	System.out.println(car);
}

SpringIOC.xml中如下

<bean id="car" class="com.rookie.Car">
	<property name="name" value="audi"/>
</bean>

ClassPathXmlApplicationContext源码中

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh,     
                 ApplicationContext parent) throws BeansException {
    // 层层往上调,最终会初始化PathMatchingResourcePatternResolver解析器
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

PathMatchingResourcePatternResolver可以用来查找类路径、jar的类路径、文件系统中的资源,资源路径可以时Ant风格或者无通配符,该类会在后面具体的解释。setConfigLocations方法在文章的末尾进行相关的解释,IOC的主核心代码为AbstractApplicationContext中的refresh方法,跟随debug的脚步来到方法中的

protected void prepareRefresh() {
    this.startupDate = System.currentTimeMillis();
    // 对当前容器的一些标识位
    this.closed.set(false);
    this.active.set(true);

    if (logger.isInfoEnabled()) {
        logger.info("Refreshing " + this);
    }

    // 容器的运行需要某些必备的环境参数
    initPropertySources();

    // 验证上一步设置的参数在当前环境中是否存在
    getEnvironment().validateRequiredProperties();

    // 存储事件集合,目前好像没用到,为什么要在此处提前初始化好?待用到时候再回来做笔记  TODO
    this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}

针对initPropertySources的扩展点,如下做一个小demo进行测试

public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {

    public MyClassPathXmlApplicationContext(String... configLocations) {
        super(configLocations);
    }

    // 重写方法
    @Override
    protected void initPropertySources() {
        getEnvironment().setRequiredProperties("abc");
    }
}

采用的时junit进行测试,当VM中没有设置abc该变量值时,会报如下的具体错误

The following properties were declared as required but could not be resolved: [abc]

在VM options中随便设置对应的参数值:-Dabc=IOC时,代码正常运行。

分析setConfigLocations方法之前,需将conf/SpringIOC.xml改为conf/Spring${abc}.xml,并且VM参数不变。有点像SpEL处理占位符。最终的结果导向是将占位符${abc}替换为IOC,分析下相关核心源代码。

AbstractRefreshableConfigApplicationContext

public void setConfigLocations(String... locations) {
	if (locations != null) {
		Assert.noNullElements(locations, "Config locations must not be null");
		this.configLocations = new String[locations.length];
		for (int i = 0; i < locations.length; i++) {
			this.configLocations[i] = resolvePath(locations[i]).trim();
		}
	}
	else {
		this.configLocations = null;
	}
}

/**
 * Resolve the given path, replacing placeholders with corresponding
 * environment property values if necessary. Applied to config locations.
 * @param path the original file path
 * @return the resolved file path
 * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
 */
// 通过容器的上下文来解析给定路径的占位符
protected String resolvePath(String path) {
	return getEnvironment().resolveRequiredPlaceholders(path);
}

PropertyPlaceholderHelper

protected String parseStringValue(String value, PlaceholderResolver placeholderResolver,     
              Set<String> visitedPlaceholders) {

	StringBuilder result = new StringBuilder(value);
    // 获取第一个占位符的开始位置
	int startIndex = value.indexOf(this.placeholderPrefix);
	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.add(originalPlaceholder)) {
				throw new IllegalArgumentException(
						"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
			}
			// 递归调用,解析占位符中是否还有占位符符号
			placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
			// 从上下文的环境中获取字符串所对应的值(K-V键值对)
			String propVal = placeholderResolver.resolvePlaceholder(placeholder);
            // 当占位符字符串是abc:MVC时,如果abc在环境中没有找到对应值,则以MVC作为默认值返回
			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;
					}
				}
			}
			if (propVal != null) {
				// 设置的k-v值是否也存在占位符,有一次的递归调用
				propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                // 真正开始替换占位符中的值,即${abc} => IOC
				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();
}

 

待处理问题点:

  1. prepareRefresh中为什么要提前初始化好存储事件集合实例 this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值