Spring学习记录(三):Spring IOC源码学习(上)

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便是我们的配置文件的名称了!从结构上来看,这个类还是让人熟悉的,其中不能一目了然的只有setConfigLocationsrefresh了。

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的相关内容,因此咱下篇文章见!(主要是不知道该怎么打标题了)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值