从源码解读Spring的IOC

概念

IOC(Inversion Of Control),即控制反转,或者说DI(Dependency Injection),依赖注入,都属于Spring中的一种特点,可以统称为IOC

  • 控制反转,即控制权的反转,也就是说将内置对象创建的控制权交给第三方容器,而不是本身的对象
  • 依赖注入,即将依赖对象进行注入,也就是说不主动创建对象,而是通过第三方容器将依赖的对象注入进来

不管是IOC还是DI,它们都有一个共同的特点,即通过第三方容器来管理对象的创建、初始化、注入、销毁,在Spring中,这些容器中的对象统称为bean,使用的时候只需要通过xml或Java的配置就能够很方便地将一个对象声明为容器中的bean,并且可以通过@Autowired注解等方法将这些bean注入到需要的地方,我们接下来就要深入地了解一下Spring中IOC到底是怎么实现的

BeanFactory

顾名思义,BeanFactory就是Bean的工厂,也就是Bean的容器,BeanFactory作为容器接口,我们暂不关心它的实现类,先了解一下它本身的特性,点进BeanFactory的源码,我们只看以下这几个重要的方法,其余方法用到的时候再说

	/** 通过name获取bean实例 */
	Object getBean(String name) throws BeansException;

	/** 通过name和对象类型获取bean实例 */
	<T> T getBean(String name, Class<T> requiredType) throws BeansException;

	/** 得到bean的别名,如果通过别名索引,则原名也会被检索出 */
	String[] getAliases(String name);

这几个方法看起来很简单,别急,这只是在接口中的定义,Spring提供了很多BeanFactory的实现类,比如ApplicationContext等

BeanDefinition

bean当然不能用普通的对象来描述,在spring中,bean被封装成BeanDefinition,如下:
图1

Bean资源的加载过程

资源的加载,也可以是认为是容器的初始化,可以分为以下三个部分:

  • 定位资源
  • 载入资源
  • 注册资源

比如XmlWebApplicationContext就是从xml文件中加载资源,我们这里以ClassPathXmlApplicationContext为例,了解一下xml文件中的配置是怎么加载到Spring容器中的

首先是构造方法,如下:

	/**
	 * @param configLocations 资源路径
	 * @param refresh 是否自动刷新容器
	 * @parent parent 容器的父类
	 */
	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {
   

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

先来看这个super(parent),我们一路找上去,发现每一层都调用了super(parent),直到进入AbstractApplicationContext类中,发现调用了一个this()方法,这个方法详细如下:

	public AbstractApplicationContext() {
   
		this.resourcePatternResolver = getResourcePatternResolver();
	}

从字面上理解,应该是类似设置资源解析器之类的方法,我们进入这个方法,发现其实际上创建了一个PathMatchingResourcePatternResolver对象,同时设置我们的最顶层容器为resourceLoader资源加载器,看到这里就差不多了解了,super(parent)实际上就是设置了bean的资源加载器

我们接着看setConfigLocations(configLocations)方法,源码如下:

	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;
		}
	}

这个方法是继承而来的,是AbstractRefreshableConfigApplicationContext的一个方法,在这个方法内部,设置了configLocations的值为资源路径(进行环境变量填补充并去除空格),可以理解为对资源进行定位

也就是说,容器在创建出来时,做了以下两件事(不包括刷新容器操作):

  • 设置资源解析器
  • 设置资源路径,进行资源定位

然后我们再来看这个可选的refresh()方法,这是从AbstractApplicationContext继承而来的方法,源码如下:

	@Override
	public void refresh() throws BeansException, IllegalStateException {
   
		synchronized (this.startupShutdownMonitor) {
   
			// 获取当前时间,同时设置同步标识,避免多线程下冲突
			prepareRefresh();

			// 实际调用了子类的refreshBeanFactory方法,同时返回子类的beanFactory
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// 设置容器属性
			prepareBeanFactory(beanFactory);

			try {
   
				// 为子类beanFactory指定BeanPost事件处理器
				postProcessBeanFactory(beanFactory);

				// 调用注册为bean的事件处理器
				invokeBeanFactoryPostProcessors(beanFactory);

				// 注册BeanPost事件处理器,用于监听容器创建
				registerBeanPostProcessors(beanFactory);

				// 初始化消息源
				initMessageSource();

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

				// 在特定的子类中初始化其他特殊的bean
				onRefresh();

				// 检查并注册监听器
				registerListeners();

				// 初始化剩余的单例
				finishBeanFactoryInitialization(beanFactory);

				// 最后一步:初始化容器生命周期处理器,并发布容器生命周期事件
				finishRefresh();
			}

			catch (BeansException ex) {
   
				if (logger.isWarnEnabled()) {
   
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// 销毁创建的单例,以避免悬置资源
				destroyBeans();

				// 重置同步标识
				cancelRefresh(ex);

				throw ex;
			}

			finally {
   
				// 因为不需要单例bean中元数据,所以重置spring的自检缓存
				resetCommonCaches();
			}
		}
	}

配合注释,就能差不多了解了执行过程,实际就是初始化并注册一系列处理器和监听器的过程,有人可能会发现,怎么没有加载资源的过程,别急,我们进入obtainFreshBeanFactory()方法,其中有一个refreshBeanFactory()方法,我们点开AbstractRefreshableApplicationContext中的实现:

	@Override
	protected final void refreshBeanFactory() throws BeansException {
   
		if (hasBeanFactory()) {
   
			destroyBeans();
			closeBeanFactory();
		}
		try {
   
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			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()方法,会根据选用的xml解析还是注解解析调用子类中的方法,再接下来的解析过程就不是我们分析的重点了,如果以后有时间,我会再写一篇来专门分析解析过程的文章

到这里,整个加载过程就清晰了

依赖注入的过程

一开始,我们就介绍了getBean(name)方法,那么我们接下来,就要详细的来进行分析这个方法到底是怎么把我们需要的bean创建并交给我们的

这个方法有两种常见的实现,AbstractBeanFactory和AbstractApplicationContext,而实际上AbstractApplicationContext也是调用了AbstractBeanFactory的方法,所以我们就只看AbstractBeanFactory即可

在这个方法内部调用了doGetBean方法,我们进入方法内部,如下:

	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
   

		final String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
   
			if (logger.isTraceEnabled()) {
   
				
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值