Spring学习记录(三):Spring IOC源码学习
文章目录
前言
上文讲述了SpringIOC依赖注入的基本使用方法,接下来通过上文中的启动配置开始分析一下源码流程,当然Spring这个庞然大物摆在这里,只能粗糙的进行概括一下~
@Test
public void test2(){
//核心接口,但是不会创建bean对象存入容器中
BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
//在getBean的时候才真正创建bean对象
UserDao userDao = (UserDao)xmlBeanFactory.getBean("UserDao");
userDao.sava();
}
@Test
public void test3(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)applicationContext.getBean("userService");
userService.save();
}
一、Spring源码学习
1.基础流程
要了解装配过程,首先得知道大致的流程,之后再进行细分,战术上要逐一击破!
首先从加载文件中读取解析文件内容(xml、yml、properties、注解、json),封装成一个BeanDefinition对象,该对象便是用来存放其属性值,经过实例化完成一个对象使用,根据调用的不同getBean方法,构造不同的对象。
2.ClassPathXmlApplicationContext
2.1 结构图
这里我打算主要讲一下ClassPathApplicationContext来进行源码分析,从而了解其装配过程,话不多说先来了解这是个什么东西,上图:
从图中可以看到左下角的ClassPathXmlApplicationContext是我们代码中new的一个东西(用起来挺熟练的~),旁边的FileSystemXmlApplicationContext则是同门师兄弟,它俩区别在于加载的位置不同,其中还需要关注一个的则是AnnotationConfigApplicationContext,他是通过注解来加载的方式,也听好多人说这个方式也是大势所趋了!
而顶端的ApplicationContext则是核心接口——Spring核心上下文!
2.2 源码
这里为了方便阅读,我删减了部分源码以及注释,详细的可以上IDEA下载源码来看,其中注释看不懂的可以下载一个translate插件边翻译边看~
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
@Nullable
private Resource[] configResources;
public ClassPathXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
}
首先看到的这个源码便是我们打了断点以后进来的地方,其中configLocation便是我们的配置文件的名称了!从结构上来看,这个类还是让人熟悉的,其中不能一目了然的只有setConfigLocations与refresh了。
2.2.1 setConfigLocations
首先我们按照顺序来看,ctrl+鼠标左键进入该方法中,我们首先可以发现他是在抽象类AbstractRefreshableConfigApplicationContext下的方法,话不多说先上代码:
public void setConfigLocations(@Nullable 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;
}
}
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
setConfigLocations其实就两个作用,一个是整理传过来的占位符,还有一个就是设置环境变量,从其中调用resolvePath可以看出他返回的是一个字符串,通过的方法是getEnvironment().resolveRequiredPlaceholders(path),其中的getEnvironment便是环境变量的一系列操作~再贴代码:
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
一点点细分下来,这里其实就是一个很简单的逻辑,没有则创建~接下来便是看一下ConfigurableEnvironment里面的内容了,首先是关于他的结构图:
我们继续进行步入,通过createEnvironment方法,我们可以看到它new了一个StandardEnvironment,继续跟入,我们来看看这个类的源码:
public class StandardEnvironment extends AbstractEnvironment {
/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
我们可以看到其中的customizePropertySources便是会往资源列表中添加Java进程中的变量和系统的环境变量。
接着我们在回到resolvePath方法上,毕竟getEnvironment走完后,后面还有一半resolveRequiredPlaceholders没有走完,上文说到该接口两个作用——处理占位符以及环境变量。如今环境变量解决,该方法自然是处理占位符,我们继续F7冲起来!咱不去管他创建啥啥助手,这些所有的目的是为了调用最后的方法——parseStringValue
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");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
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;
}
}
}
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();
}
这里主要处理了使用${}占位符的一些地方。
2.2.2 refresh
看完setConfigLocations之后便是一个判断走入refresh()方法,这个方法的内容会比较多,老操作先上源码(为了方便,在每个方法前写上了基本的功能):
@Override
public void refresh() throws BeansException, IllegalStateException {
//加锁防止refresh还没结束,容器又进行其他操作
synchronized (this.startupShutdownMonitor) {
//进行准备工作,记录容器的启动时间并标记启动状态以及处理配置文件中的占位符
prepareRefresh();
//这里会把配置文件解析出一个个bean出来,但是还没有初始化
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//设置 BeanFactory 的类加载器
prepareBeanFactory(beanFactory);
try {
// 提供了一些拓展的方式,这边还是很牛的
postProcessBeanFactory(beanFactory);
//调用BeanFactoryPostProcessor各个实现类的postProcessBeanFactory(factory)方法
invokeBeanFactoryPostProcessors(beanFactory);
// 注册
registerBeanPostProcessors(beanFactory);
//初始化当前ApplicationContext的MessageSource
initMessageSource();
// 初始化当前ApplicationContext的事件广播器
initApplicationEventMulticaster();
// 钩子方法
onRefresh();
// 注册事件监听器,监听器需要实现ApplicationListener接口
registerListeners();
//初始化所有的singleton beans
finishBeanFactoryInitialization(beanFactory);
// 广播
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
看到这么多方法自然是要一个个F7的~
2.2.2.1 prepareRefresh
首先看一下prepareRefresh。
protected void prepareRefresh() {
// Switch to active.
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isDebugEnabled()) {
if (logger.isTraceEnabled()) {
logger.trace("Refreshing " + this);
}
else {
logger.debug("Refreshing " + getDisplayName());
}
}
// Initialize any placeholder property sources in the context environment.
initPropertySources();
// Validate that all properties marked as required are resolvable:
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
// Store pre-refresh ApplicationListeners...
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
// Reset local application listeners to pre-refresh state.
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}
这里的initPropertySources()方法内部并没有做任何事情,spring的目的估计是留给设计者们根据需求自行设计的。validateRequiredProperties()看名字就知道是校验用的,步入看看里面的内容我们可以知道,其本质内容可以理解为当value为空时抛出异常,停止spring。当然在企业级开发中,我们可以将需要提前校验的东西放入getEnvironment()中,这样在这里进行校验的时候可以一并验证进去。
@Override
public void validateRequiredProperties() {
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
for (String key : this.requiredProperties) {
if (this.getProperty(key) == null) {
ex.addMissingRequiredProperty(key);
}
}
if (!ex.getMissingRequiredProperties().isEmpty()) {
throw ex;
}
}
2.2.2.2 obtainFreshBeanFactory
这里完成了BeanFactory的初始化,Bean的加载以及注册(很重要)。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
一看就知道先刷新后获得,常规操作。但是后面涉及到BeanFactory的相关内容,因此咱下篇文章见!(主要是不知道该怎么打标题了)