Spring 源码分析(2)--SpringIoC源码分析

IoC源码分析篇

参考博客:https://www.cnblogs.com/ITtangtang/articles/3978349.html#a3
xsd学习:https://www.w3school.com.cn/schema/schema_simple_attributes.asp
dtd学习:
https://www.w3school.com.cn/dtd/dtd_intro.asp

注意:本文基于spring-5.0.4.RELEASE版本源码的研究,本文叙述过程比较口水化,敬请包涵、谅解。

1、正文开始

spring官网文档:

https://docs.spring.io/spring/docs/5.0.4.RELEASE/spring-framework-reference/core.html#beans
研究Spring可以结合官网文档,学习效率可能加倍,不过要求你英文水平过关。废话少说,下文进行主题。


首先,我们看源码结构:(不知道如何下载源码并导入IDEA的小伙伴,可以参考Spring源码分析(1)–准备篇

在这里插入图片描述

我们从源码看出,spring项目由多个子模块组成,如:
spring-beans:主要放置BeanFactory、FactoryBean、DefaultListableBeanFactory、InitializingBean、BeanWrapper等,IoC将依赖该模块
spring-context:主要有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、ApplicationContext、ApplicationContextAware等构成IoC容器基础的上下文
spring-core:spring的核心包,包括一些基础工具,如cglib、断言Assert、Base64Utils等,并提供annotation、convert的支持。
spring-aop:aop模块
spring-web:http相关一些工具类,在spring集成其他框架技术的时候会依赖该模块,如redistemplate等
spring-webmvc:springmvc模块


2、Spring IoC源码分析

源码工程好像没有所谓的spring-ioc?那么我要看IoC要哪里入手呢?
不知道大家还记不记得写过的Spring的hello world的代码,不记得,没关系,下面的代码我们一起回忆一波!


找到spring-test工程,创建Test类,然后输入spring入门hello world代码:
public class Test {
   

	public static void main(String[] args) {
   

		ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
		Object bean = context.getBean(Bea.class);

	}

}

application-context.xml配置文件:
application-context配置文件
现在有印象了吧,但之前我们只知道,写配置文件配置好我们需要的Bean,然后把配置文件交给Spring容器,它就可以帮我们自动构建我们需要的Bean对象。但是这个所谓的“自动”过程是怎样的呢?下面开始IoC源码的讲解,请人手一份spring-5.0.4.RELEASE版本的源码,跟着我的思路一起走。

2.1、IoC容器的初始化

spring-IoC时序图
时序图大图查看下载地址:https://www.processon.com/view/5d8a3140e4b011ca2aad49a1
时序图看第一遍不懂没关系,后面总结还会提到。

Spring IoC初始化分三部曲:定位加载注册,相信从别的博客,或者学习视频都能听到这几个名词,站在前辈的肩膀上学习,总能看得更远,更清晰,本文也是从其他博客学习借鉴过来的。下面我们带着对三部曲的疑惑,进入初始化过程:


2.1.1、配置资源的定位

点进Test.java的ClassPathXmlApplicationContext构造器,进入带String参数的构造器

ClassPathXmlApplicationContext:

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
   
		this(new String[] {
   configLocation}, true, null);
	}

接下来进入this(new String[] {configLocation}, true, null);

如果你使用的IDE是IntelliJ IDEA,可以将光标放在this上,然后Ctrl + Alt + B 进入对应的实现,该方式的好处是:当调用接口或者抽象的抽象方法的时候,能直接进入对应的实现方法。

ClassPathXmlApplicationContext:

public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {
   

		//设置传入进来的父容器、创建Bean资源解析器
		super(parent);  // 代码1

		//将传入进来的配置文件路径(如spring/aaa.xml,spring/bbb.xml)保存起来
		setConfigLocations(configLocations); // 代码2

		if (refresh) {
   

			// 初始化最核心的方法,就是refresh()
			// 刷新过程将会将传入的资源(配置文件等)转换为BeanDefinition
			// 然后根据BeanDefinition生成Bean,再触发DI注入操作,为Bean的属性赋值
			// 主线
			refresh(); // 代码3 
		}
	}

注:阅读源码一定切忌陷入细节当中,导致头脑混乱。以下过程仅列出主线代码,分支代码详情可以点对应提示的链接观看。

上述代码分三步走,接下来介绍具体的功能:
代码1,调用父类AbstractApplicationContext的构造器,创建一个资源解析器,并将传递进来的parent容器保存起来(如果父容器不为空),然后将父容器的环境合并到当前容器环境中去。
详情可看:super(parent)

代码2,调用父类AbstractRefreshableConfigApplicationContext实现的setConfigLocations(String… locations)方法,将参数"application-context.xml"保存到AbstractRefreshableConfigApplicationContext的成员变量中,该成员变量将在代码3中提到如何使用,先做个铺垫。
详情可看:setConfigLocations(configLocations)

代码3,这一步是初始化过程最重要的一步,本文接下来将大概(将忽略不重要的步骤)介绍初始化的刷新过程。请根据上文提供的时序图,一起来看看refresh执行了哪些操作。

Ctrl + Alt + B 进入refresh对应实现:
由AbstractApplicationContext提供对应的refresh()方法的实现:

AbstractApplicationContext:

public void refresh() throws BeansException, IllegalStateException {
   
		synchronized (this.startupShutdownMonitor) {
   
			//调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
			prepareRefresh();

			//获取一个可刷新的BeanFactory,里面包括了一个抽象方法refreshBeanFactory();
			//用来执行子类的刷新,由子类实现
			//主线
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			//为BeanFactory配置容器特性,例如类加载器、事件处理器等
			prepareBeanFactory(beanFactory);

			try {
   
				//为容器的某些子类指定特殊的BeanPost事件处理器
				postProcessBeanFactory(beanFactory);

				//调用所有注册的BeanFactoryPostProcessor的Bean
				invokeBeanFactoryPostProcessors(beanFactory);

				//为BeanFactory注册BeanPost事件处理器
				//BeanPostProcessor是Bean后置处理器,用于监听容器出发的事件
				registerBeanPostProcessors(beanFactory);

				//初始化信息源,和国际化有关。
				initMessageSource();

				//初始化容器事件传播器
				initApplicationEventMulticaster();

				//调用子类的某些特殊Bean初始化方法
				onRefresh();

				//为事件传播器注册事件监听器
				registerListeners();

				//重点  重点  重点 
				//初始化所有的设置lazy-init=false的bean
				finishBeanFactoryInitialization(beanFactory); //代码4

				//初始化容器的生命周期事件处理器,并发布容器的生命周期事件
				finishRefresh();
			}

			catch (BeansException ex) {
   
				...
			}

			finally {
   
				...
			}
		}
	}

AbstractApplicationContext的refresh()是一个模板方法,我们接下来进入“主线”来看接下来如何执行实际上的refresh功能。
其中代码4 ,将触发依赖注入,后续会详细讲述。DI入口1

AbstractApplicationContext:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   

		//调用由子类提供的刷新BeanFactory的方法
		//主线
		refreshBeanFactory();

		//调用子类提供的,获取BeanFactory的方法
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
   
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}

obtainFreshBeanFactory()方法使用了委派模式,本身自己不做刷新功能,而是将实际刷新任务委派给子类
AbstractRefreshableApplicationContext的refreshBeanFactory()方法。我们从命名其实可以看出来,带有Refreshable的单词AbstractRefreshableApplicationContext类实际上,提供了刷新功能的实现。

Ctrl + Alt + B 进入refreshBeanFactory方法对应实现,进入AbstractRefreshableApplicationContext的实现:

AbstractRefreshableApplicationContext:

protected final void refreshBeanFactory() throws BeansException {
   
		//假如存在BeanFactory,则销毁、关闭它
		if (hasBeanFactory()) {
   
			destroyBeans();
			closeBeanFactory();
		}
		try {
   
			//创建新的BeanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);

			//加载bean定义
			//主线
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
   
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
   
			...
		}
	}

在AbstractRefreshableApplicationContext的refreshBeanFactory()方法中,主要做了四件事:
1)销毁关闭存在的BeanFactory
2)创建一个新的BeanFactory,其对应实现为DefaultListableBeanFactory


这里开始,BeanFactory开始登场,首先我们先查看下createBeanFactory方法对应的实现:

AbstractRefreshableApplicationContext:

protected DefaultListableBeanFactory createBeanFactory() {
   
		return new DefaultListableBeanFactory(getInternalParentBeanFactory());
	}

AbstractRefreshableApplicationContext的createBeanFactory方法事实上创建的默认BeanFactory是DefaultListableBeanFactory,下面我们看下DefaultListableBeanFactory与BeanFactory的关系图:
在这里插入图片描述
由图可看出,BeanFactory有三个子接口ListableBeanFactory、AutowireCapableBeanFactory、HierarchicalBeanFactory,分别代表的功能为ListableBeanFactory表示 Bean 是可列表的、AutowireCapableBeanFactory表示Bean 是可自动装配的、HierarchicalBeanFactory 表示Bean 是有继承关系的,也就是每个Bean 有可能有父 Bean。而DefaultListableBeanFactory实现了上述接口的全部功能。

3)进行Spring IoC初始化三步曲的加载步骤。(接下来2.1.2重点介绍)
4)将其创建好并自定义化后的DefaultListableBeanFactory保存到AbstractRefreshableApplicationContext的成员变量域中,为AbstractRefreshableApplicationContext的getBeanFactory提供值。当前先做铺垫,后面会介绍如何使用。

2.1.2、加载资源文件

Ctrl + Alt + B进入loadBeanDefinitions(beanFactory)对应的实现,AbstractXmlApplicationContext提供如下实现:

AbstractXmlApplicationContext:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
   
		
		//创建一个Xml的BeanDefinition的读取器
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		
		//为BeanDefinition读取器配置当前上下文的资源加载环境
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		//允许子类提供BeanDefinition的自定义初始化功能
		initBeanDefinitionReader(beanDefinitionReader);
		//执行事实上的加载
		//主线
		loadBeanDefinitions(beanDefinitionReader);
	}

进入主线loadBeanDefinitions方法:

AbstractXmlApplicationContext:

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
   
		
		//如果存在Resource资源配置,则进行加载		
		//当前获取的返回值为null,由于没有使用到构造器
		//ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent)
		//因此没有为Resource[] configResources赋值
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
   
			reader.loadBeanDefinitions(configResources);
		}

		//getConfigLocations()返回ClassPathXMLApplicationContext第二步
		//setConfigLocations(configLocations)设置进来的xml配置文件的路径(即application-context.xml的路径)
		// 由AbstractRefreshableConfigApplicationContext提供具体实现
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
   
			//主线
			//以下步骤将会把configLocation转化为Resource,其实最终调用的还是reader.loadBeanDefinitions(configResources)的方法
			reader.loadBeanDefinitions(configLocations);
		}
	}

接下来需要重点介绍一下XmlBeanDefinitionReader,我们先看下其继承哪些父类,实现了哪些接口:
在这里插入图片描述
由上图可看出,XmlBeanDefinitionReader继承了AbstractBeanDefinitionReader抽象类,并实现了BeanDefinitionReader接口。事实上,XmlBeanDefinitionReader只实现了int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException方法。其他定义在BeanDefinitionReader接口的抽象方法都由AbstractBeanDefinitionReader提供实现。接下来我们来看看XmlBeanDefinitionReader是如何实现加载BeanDefinition的。

Ctrl + Alt + B进入reader.loadBeanDefinitions(configLocations),该方法由AbstractBeanDefinitionReader提供对应实现:

AbstractBeanDefinitionReader:

    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
   
		Assert.notNull(locations, "Location array must not be null");
		int counter = 0;
		for (String location : locations) {
   
			counter += loadBeanDefinitions(location);
		}
		return counter;
	}
	
	public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
   
		return loadBeanDefinitions(location, null);
	}	

以上步骤加载的资源配置,还是本地资源的配置,该配置并不能让Spring解析识别,在接下来的步骤,将会统一转换为Resource对象:

AbstractBeanDefinitionReader:

    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
   
        //获取资源加载器,该资源加载器事实上是ClassPathXmlApplicationContext对象,从哪里得知呢?
		ResourceLoader resourceLoader = getResourceLoader(); //代码5
		if (resourceLoader == null) {
   
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}
		
		//由于ClassPathXmlApplicationContext实现ResourcePatternResolver,将执行以下分支
		if (resourceLoader instanceof ResourcePatternResolver) {
   
			try {
   
			    //将字符串类型的配置文件转换为Spring能识别的Resource对象资源
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);//代码6
				//进入重载的loadBeanDefinitions(Resource..)方法
				//最终调用由XmlBeanDefinitionReader实现的loadBeanDefinitions(Resource resource)方法
				//主线
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
   
					for (Resource resource : resources) {
   
						actualResources.add(resource);
					}
				}
				...
				return loadCount;
			}
			catch (IOException ex) {
   
                ...
			}
		}
		else {
   
			//仅可以从绝对路径加载单独的resource
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
   
				actualResources.add(resource);
			}
            ...
			return loadCount;
		}
	}

代码5:getResourceLoader()返回由AbstractXmlApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中beanDefinitionReader.setResourceLoader(this)设置进来的ClassPathXmlApplicationContext对象
详情可参考:getResourceLoader()

代码6:
((ResourcePatternResolver) resourceLoader).getResources(location)为AbstractApplicationContext实现的getResources方法。此时即调用ClassPathXmlApplicationContext对象的getResources方法,其由父类AbstractApplicationContext提供实现,返回值为ClassPathXmlApplicationContext代码1 super(parent)初始化时,调用AbstractApplicationContext()方法创建的PathMatchingResourcePatternResolver对象。
详情可参考:((ResourcePatternResolver) resourceLoader).getResources(location)

继续看主线,主线调用AbstractBeanDefinitionReader重载的loadBeanDefinitions(Resource… resources)方法:
AbstractBeanDefinitionReader:

    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
   
		Assert.notNull(resources, "Resource array must not be null");
		int counter = 0;
		for (Resource resource : resources) {
   
            //主线
			counter += loadBeanDefinitions(resource);
		}
		return counter;
	}

接着调用抽象方法loadBeanDefinitions(Resource resource),由XmlBeanDefinitionReader提供实际上的实现:

XmlBeanDefinitionReader:

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   
		return loadBeanDefinitions(new EncodedResource(resource));
	}
	
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   
		...
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值