不看后悔系列-Spring源码(二):IOC阅读导航篇

深入剖析Spring框架的核心——IOC容器的初始化和bean管理机制,详细解读控制反转(IOC)与依赖注入(DI)的概念,以及Spring源码中如何实现资源定位、解析和BeanDefinition的注册。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面我们介绍了Spring的整体内容,本章开始就直奔主题(上干货啦!打起精神!),开始源码的讲解。
看官你要知道Spring core的核心在于对bean的管理,核心概念就IOC,那么第一部分我们就来瞅一瞅SpringIOC!!!

一、IOC介绍

IoC 全称为 Inversion of Control,翻译为 “控制反转”
控制反转”就是由 Spring IOC 容器来负责对象的生命周期和对象之间的关系

还有一个引申的概念,DI

DI全称(Dependency Injection),翻译为“依赖注入”
依赖注入是指对象属性的设置

我知道如果我写到这就戛然而止肯定会被打的,所以接下来我们就透彻解析一波!!!

首先解释一下控制的概念,控制就是对象的控制。我们知道一般情况下我们创建对象都是通过new关键字来创建,比如我们创建一个小明同学

Student xiaoming=new Student()

ok,这样就创建了小明同学这个对象,那么就是我们自己控制了对象的创建,想什么时候创建就什么时候创建(new就完了)。
所以当我们把控制权交出去,就是给Spring来创建对象(Spring帮助我们new),那么这就是控制反转
了解了控制反转,再来谈一谈DI,DI就是所谓的依赖注入。你想啊,有个对象还不行啊,我们还得给对象属性值啊,比如还是上面的例子

Student xiaoming=new Student()
xiaoming.setAge(20)         //设置小明年龄20岁
xiaoming.setWeght(100)   //设置小明体重100公斤(真重!要减肥啊)

这里的set方法对应的就是依赖,而注入指的是把这个依赖给刚刚Spring创建的对象设置属性。

嗯?不懂?再去看一遍!!!

下面,登登登!进入正题了,首先给你明确几个概念,你如果是刚开始看,可能会不理解,没关系,只要你记得等你学完之后回头再多来看看这些,你会有一种恍然大悟的感觉。(初学者切忌心浮气躁)
概念精简且简单,go~

IOC的整个过程分为两部分

  1. 容器初始化阶段
    容器初始化阶段分为三个过程
    1)资源定位:定位资源文件,比如xml(将资源文件转化成Resource)
    2)解析、装载:将资源文件中的标签进行解析,转换成BeanDefinition
    3)BeanDefinition注册:将BeanDefinition注册进beanFactory
  2. 加载 bean 阶段

整个IOC就是如此,下面开始我们的学习吧!

二、IOC第一阶段——容器初始化阶段

既然要看源码,肯定要找个入口,不然没头没脑的乱撞最后啥也不知道。所以先准备一下Spring的例子项目,这里我们从以下入口开始看:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-bean.xml");           //创建Spring容器
TestBeanServiceImpl bean = context.getBean(TestBeanServiceImpl.class);  //通过容器获取对象
bean.getBean();                                            //执行方法
context.close();                                           //关闭容器

测试项目下载spring源码测试项目下载

如果你对这段代码一点都不熟悉,退出吧,看源码还不适合你

ClassPathXmlApplicationContext 是Spring的容器,Spring把所有对象的管理都交给这个容器来管理。创建Spring容器对象便完成了Spring的核心工作了,所以我们主要看这个new ClassPathXmlApplicationContext(“spring-bean.xml”);方法。
我们首先来看下容器的类图结构。(这个类图其实不是让你看懂的,如果你还是第一次看的话。但是可以帮助你以后的理解,常回来看看,嘿嘿)
容器类图结构
接下来各位看官我们就开始啦!拍拍脸清醒清醒
提示:打开示例项目跟着我走啊!!!光看不可能懂得!!!

首先我们进入new ClassPathXmlApplicationContext(“spring-bean.xml”)这行代码

/**
	 * Create a new ClassPathXmlApplicationContext, loading the definitions
	 * from the given XML file and automatically refreshing the context.
	 * @param configLocation resource location
	 * @throws BeansException if context creation failed
	 */
	public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

继续

/**
	 * Create a new ClassPathXmlApplicationContext with the given parent,
	 * loading the definitions from the given XML files.
	 * @param configLocations array of resource locations
	 * @param refresh whether to automatically refresh the context,
	 * loading all bean definitions and creating all singletons.
	 * Alternatively, call refresh manually after further configuring the context.
	 * @param parent the parent context
	 * @throws BeansException if context creation failed
	 * @see #refresh()
	 */
	public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

发现来到了位于ClassPathXmlApplicationContext中的这个方法,看注释知道这是一个构造ClassPathXmlApplicationContext 的方法。

1. super(parent);

ApplicationContext 允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。对于Bean 的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的Spring 应用提供了一个共享的Bean 定义环境。

跟到父类发现是调用AbstractApplicationContext中的构造方法


/**
* Create a new AbstractApplicationContext with the given parent context.
 * @param parent the parent context
 */
public AbstractApplicationContext(ApplicationContext parent) {
	this();
	setParent(parent);
}

其中this()中做了一件事,初始化resourcePatternResolver ,看名字知道这是一个解析Resource的对象(后面会用到)

/**
 * Create a new AbstractApplicationContext with no parent.
 */
public AbstractApplicationContext() {
	this.resourcePatternResolver = getResourcePatternResolver();
}

然后setParent(parent);设置了父容器(后面会用到),因为这里传的null,所以没有父容器。

2.setConfigLocations(configLocations);

setConfigLocations(configLocations)的作用: 用来解析Bean定义资源文件的路径,处理多个资源文件字符串数组,并将它保存到成员变量里去。 这个存放的东西,等到我们在后面需要getResource的时候,就要用到(在loadBeanDefinitions方法里面)。

这个方法的代码为

/**
* Set the config locations for this application context.
 * <p>If not set, the implementation may use a default as appropriate.
 */
public void setConfigLocations(String... locations) {
	if (locations != null) {
		Assert.noNullElements(locations, "Config locations must not be null");  //判断locations是否为null
		this.configLocations = new String[locations.length];                    //初始化本地configLocations数组
		for (int i = 0; i < locations.length; i++) {
			this.configLocations[i] = resolvePath(locations[i]).trim();         //关键点
		}
	}
	else {
		this.configLocations = null;
	}
}

我们这边主要解析一下关键点这行代码,看一下这个resolvePath方法

/**
 * 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);
}

这里就要引申出第一个体系:不看后悔系列-Spring源码(三):Environment接口体系
具体内容跳转:

接下来继续我们的源码解析
3. refresh();
这里判断了一下refresh这个参数,自己去看看refresh参数设置的什么!
ok!接下来所有的内容都是围绕这个refresh();来讲解的。可以看到这个refresh是位于AbstractApplicationContext类中实现的,去看看之前的类继承图,你会发现所有的容器最后都会调用这个refresh();方法来给自己进行初始化!(用了模板方法设计模式
这也是为什么这一个示例代码就可以了解整个Spring IOC的流程了。
那么看官和我一起来看看这个refresh();有何玄机~

refresh()作用:在创建IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh 之后使用的是新建立起来的IOC 容器。refresh 的作用类似于对IOC 容器的重启,在新建立好的容器中对容器进行初始化,对Bean 定义资源进行载入。

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			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();
		}
	}
}

首先一上来就用synchronized 加了个锁,实现同步
然后

  1. prepareRefresh();
    从方法注释可以了解到这是为context做一些准备工作的方法,具体代码如下,方法实现很简单,不做过多介绍。自己看看注释理解下。
/**
 * Prepare this context for refreshing, setting its startup date and
 * active flag as well as performing any initialization of property sources.
 */
protected void prepareRefresh() {
	this.startupDate = System.currentTimeMillis();       //设置本次刷新开始时间
	this.closed.set(false);                              //设置context状态为未关闭
	this.active.set(true);                               //设置context当前活跃状态为true

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

	// Initialize any placeholder property sources in the context environment
	initPropertySources();                              //初始化environment占位符source,这里为空实现
													    //有兴趣可以看看它的其他子类相关的实现          

	// Validate that all properties marked as required are resolvable
	// see ConfigurablePropertyResolver#setRequiredProperties
	getEnvironment().validateRequiredProperties();                  //验证标记为required的属性是否可以解析(xml中的属性是可以设置标识required为true的)

	// Allow for the collection of early ApplicationEvents,
	// to be published once the multicaster is available...
	this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}
  1. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    这行代码比较关键,实现了将资源文件转化为beanDefinition,并且注册到beanFactory中。
    看下代码实现
/**
 * Tell the subclass to refresh the internal bean factory.
 * @return the fresh BeanFactory instance
 * @see #refreshBeanFactory()
 * @see #getBeanFactory()
 */
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	refreshBeanFactory();                                             //关键点1
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();   //关键点2
	if (logger.isDebugEnabled()) {
		logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
	}
	return beanFactory;
}

这个方法比较好理解,关键点1就是初始化BeanFactory,然后在关键点2得到BeanFactory。关键点1是核心,后面在分析,我们先看一下关键点2,这里有一个比较有意思的实现。

  • 关键点2
    我们看下getBeanFactory();的实现(此方法位于AbstractApplicationContext中)
/**
 * Subclasses must return their internal bean factory here. They should implement the
 * lookup efficiently, so that it can be called repeatedly without a performance penalty.
 * <p>Note: Subclasses should check whether the context is still active before
 * returning the internal bean factory. The internal factory should generally be
 * considered unavailable once the context has been closed.
 * @return this application context's internal bean factory (never {@code null})
 * @throws IllegalStateException if the context does not hold an internal bean factory yet
 * (usually if {@link #refresh()} has never been called) or if the context has been
 * closed already
 * @see #refreshBeanFactory()
 * @see #closeBeanFactory()
 */
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

这里定义了一个抽象方法让子类去实现。返回了ConfigurableListableBeanFactory

这里重写方法实现在AbstractRefreshableApplicationContext类中(绕晕的话去看前面的类设计图!

@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
	synchronized (this.beanFactoryMonitor) {
		if (this.beanFactory == null) {
			throw new IllegalStateException("BeanFactory not initialized or already closed - " +
					"call 'refresh' before accessing beans via the ApplicationContext");
		}
		return this.beanFactory;
	}
}

这里是一个委派设计模式,自己定义一个抽象方法,上层直接调用这个抽象方法,然后方法实现交给子类。稍微理解理解,以后写代码不再是if - else 了,嘿嘿。
有关beanFactory的介绍

  • 关键点1
    refreshBeanFactory();这个方法也是使用了委派设计模式,这里具体实现在AbstractRefreshableApplicationContext中,看下代码:
/**
 * This implementation performs an actual refresh of this context's underlying
 * bean factory, shutting down the previous bean factory (if any) and
 * initializing a fresh bean factory for the next phase of the context's lifecycle.
 */
@Override
protected final void refreshBeanFactory() throws BeansException {
	if (hasBeanFactory()) {       //是否存在beanFactory(),如果存在,则销毁(保证只会有一个beanFactory)
		destroyBeans();
		closeBeanFactory();
	}
	try {
		DefaultListableBeanFactory beanFactory = createBeanFactory();   //创建一个新的beanFactory 对象(会得到一个DefaultListableBeanFactory对象,所有的beanFactory都是最终由它来实现的)
		beanFactory.setSerializationId(getId());                        //通过序列化保存当前beanFactory对象(有意思的实现,如果需要,可以再利用这个id获得现在状态的beanFactory对象)
		customizeBeanFactory(beanFactory);
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}

这个方法调用loadBeanDefinitions(beanFactory);最终会进入AbstractXmlApplicationContext的loadBeanDefinitions方法

**
 * Loads the bean definitions via an XmlBeanDefinitionReader.
 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
 * @see #initBeanDefinitionReader
 * @see #loadBeanDefinitions
 */
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// Create a new XmlBeanDefinitionReader for the given BeanFactory.
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

	// Configure the bean definition reader with this context's
	// resource loading environment.
	beanDefinitionReader.setEnvironment(this.getEnvironment());
	beanDefinitionReader.setResourceLoader(this);
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

	// Allow a subclass to provide custom initialization of the reader,
	// then proceed with actually loading the bean definitions.
	initBeanDefinitionReader(beanDefinitionReader);
	loadBeanDefinitions(beanDefinitionReader);
}

在具体解析之前,需要先了解一下第三个体系不看后悔系列-Spring源码(五):Resource、ResourceLoader接口体系

接下来我们继续看loadBeanDefinitions这个方法,在看了Resource和ResourceLoader之后,各位看官应该知道了前面介绍有关解析资源文件占位符等知识其实都是为了IOC第一阶段的第一个步骤(定位资源文件)做准备。
而loadBeanDefinitions属于一个核心方法,在这个方法里面完成了IOC第一阶段,接下来我们就仔细分析一下这个方法。

关于loadBeanDefinitions具体解析请跳转不看后悔系列-Spring源码(六):IOC的资源文件定位、解析和BeanDefinition装载、注册

在完成上面的阅读之后,相信看官对于IOC整个过程的第一阶段已经非常的熟悉了,下面贴一张从别人那拷的图加深理解
在这里插入图片描述


二、IOC第二阶段——加载 bean 阶段

在讲解IOC第一阶段的时候,我们是从refresh()方法开始,现在我们让refresh方法先告别一段落(后面再讲解),直接针对第二阶段的核心方法进行解析。

具体内容请跳转:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值