一文简述IOC

概述

ioc容器作为spring框架核心组件之一,此篇文章对其进行简述分析
概念:
ioc全称Inversion of Control ,翻译为控制反转;
那么什么是控制反转?

在讨论这个问题前先看下对象的创建有哪些方式?

//new
Student stu=new Student();

//使用反射1
Class clazz =Class.forName("xxx.Student");
Student stu=(Student)clazz.newInstance();

//使用反射2 使用构造器类
Class clazz =Class.forName("xxx.Student");
Constructor con=clazz .getConstructor();
Object obj=con.newInstance();

在平常的代码编写中,一般多为使用第一种方式,直接了当的new出一个对象,具体何时new,怎么new都由我们自己控制,对于控制欲较强的程序员来讲是如此的可爱,这种方式我们姑且称其为正转。虽然控制能让人身心愉悦,但是当控制的资源和关系一旦多了起来,事情就会变得复杂,那么这个时候要是有个掌柜或者经理来帮我们打理这些资产就好了。幸运的是,IOC就是这么个角色,它能够帮我们打理我们写的众多的复杂纷扰的各种类对象。当我们让ioc来管理这些类时,我们必然是要给予ioc信任并赋予一定的权限,需要告诉它哪些类是属于你管的,让管的才能管,没让它动的不能瞎搞。

那么我们将我们的类的管理权委托给了ioc容器,那么ioc又是如何管理的呢?首先回到对象的创建方式,很显然排除第一种使用new关键字的方式,因为这种方式需要提前知道类名,而Spring是个框架,不可能知道我们取得类名是多么的奇葩,so,使用反射是其必然的选择。只需要以一个字符串的形式告诉它需要实现哪个包下的哪个类即可。其次,既然我们确定了ioc创建对象的方式,那么我们如何才能告诉它我们要给它哪些类呢?答案很简单,我们只需要和ioc约定好在某一个地方放置好一个“授权文件”,明确告诉它有哪些类是需要它管理的,当它启动时去找这个“授权文件”,按图索骥,一切都被安排的明明白白。在spring中,xxx.xml就是这个角色,在xxx.xml中按照一定的格式告诉ioc需要让其管理的类。

所以,将对象的创建,管理的权限交由其他代码(框架)实现称为***控制反转***,将在ioc管理的类称之为bean。

那么,ioc是如何一步一步的把把对象创建出来的呢?
若交由我们来设计,我们会怎么实现呢?首先,作为一名合格的“管家”,需要知道的第一件事情必然是其工作对象,即要管理哪些类,所以,首先需要做的就是从xml文件中读取出bean的信息,而这些信息中就包含这个bean是哪个类,这个类对象在ioc中的名字叫什么等等一些属性。我们姑且将读取xml中bean信息的工具叫做BeanDefinitionReader,而将从xml中读取出来的bean信息封装为一个类(java中万物皆对象),姑且将其称为BeanDefinition,有了这些bean信息后,总得要开始创建对象了吧。然而,零零散散的多不好,在一个工厂里面统一创建它不香吗。所以,我们还需要造个工厂,把bean信息给到工厂(相当于给了产品的设计图纸),由工厂按照设计造出我们想要的产品,这个工厂的名字我们就叫做BeanFactory。有了工厂我们开始造个产品,然而正当我们撸起袖子准备开干的时候,甲方爸爸要该需求那该如何是好?总不能将他拒之门外啊。所以我们还需要一个“销售经理”,在给了设计图后,开始造产品前,去接收甲方爸爸添加或者修改一些奇葩需求,而这个经理的角色我们姑且称之为BeanFactoryPostProcessors,即我们允许通过它来继续增加或者修改bean的设计。终于,甲方爸爸明确了需求,我们开始造产品,这时我们想总得要有个“车间主任”吧,这个车间主任能在造产品的前后告诉我们一些记录,万一员工偷懒瞎搞可咋办,这个车间主任我们姑且称之为BeanPostProcessors。历经曲折,终于将产品造好,下一步要做的自然就是入库了,而仓库我们自然是使用Map<String,Object> 来充当了,以bean的id为键,其对象为值,存入仓库,等待甲方爸爸的召唤。然而有了这些还是不够,毕竟老板对于主任也不能完全信任,所以我们还得加个摄像头,有什么风吹草动都要第一时间告知老板,在这里我们将摄像头姑且称之为ApplicationListener,以此监听器监听ioc的整个过程。此时,我们对于ioc大致的设计就告一段落了,以下是设计图:
在这里插入图片描述

源码分析

在上节中对ioc的实现进行了设想,那么事实上的ioc容器是否是如我们设想一样的实现。查看源码一探究竟:
代码入口:

 @Test
    public  void func2(){
        String config="bean.xml";
        //从类路径中加载配置文件
        ApplicationContext ac=new ClassPathXmlApplicationContext(config);
        Func1_Service func1=(Func1_Service) ac.getBean("func1");
        func1.func();
    }

入口很简单,只是一个ApplicationContext ac=new ClassPathXmlApplicationContext(config);语句,在spring的官方文档中,

The org.springframework.beans and org.springframework.context packages are the basis for Spring Framework’s IoC container. The BeanFactory interface provides an advanced configuration mechanism capable of managing any type of object. ApplicationContext is a sub-interface of BeanFactory. It adds:

  • Easier integration with Spring’s AOP features
  • Message resource handling (for use in internationalization)
  • Event publication
  • Application-layer specific contexts such as the WebApplicationContext for use in web applications.

官网说的很详细,但是大致可以将其理解为就是一个ioc容器,也就是我们上面说的管家,通过这个“管家”,我们能够获取到它的一些内部功能(aop,事件发布。。。)和我们交由他管理的资产(bean)。
那么它是怎么实现的呢?或者说它是怎么初始化的呢?

public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {
		//初始化父容器
		super(parent);
		//设置加载项
		setConfigLocations(configLocations);
		if (refresh) {
		//刷新容器
			refresh();
		}
	}

1 配置环境

进入 super(parent)方法内部,一直到达AbstractApplicationContext父类,

	public AbstractApplicationContext(@Nullable ApplicationContext parent) {
	
		this();
		setParent(parent);
	}

	public AbstractApplicationContext() {
		//设置资源解析器
		this.resourcePatternResolver = getResourcePatternResolver();
	}
		
	public void setParent(@Nullable ApplicationContext parent) {
		this.parent = parent;
		if (parent != null) {
			Environment parentEnvironment = parent.getEnvironment();
			if (parentEnvironment instanceof ConfigurableEnvironment) {
				getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
			}
		}
	}

逻辑很简单,只是给父容器设置一些属性值。
那么看下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;
		}
	}

此段代码功能是将我们写好的配置文件(bean.xml…可以多个)地址告知ioc容器;

小结:
设置父容器,并设置一些属性值,并将bean的配置文件地址传入ioc容器

2 刷新容器----refresh

当我们已经将配置文件传入ioc容器,此时我们的ioc管家已经知道要去哪读取我们给他提供的“授权文件”并开始生产我们需要其创建的产品(bean)了,refresh()具体过程源码如下:

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			//准备上下文
			prepareRefresh();
			//创建bean工厂
			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			//配置在此上下文中使用bean工厂
			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				//abstract空方法,允许子类对bean工厂进行加强处理
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);
				//执行在此上下文中注册为bean的工厂增强处理器
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);
				//注册bean增强处理器
				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				//为上下文初始化消息源
				// Initialize message source for this context.
				initMessageSource();
				//为上下文初始化事件多播器
				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();
				//初始化在特定的上下文子类中其它特殊的bean,默认不做任何事情
				// 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();
			}
		}
	}

通过refresh方法我们可以看到,ioc开始准备上下文到创建bean之间要经历众多过程,那么下面对此过程一个个的进行分析,看看他们做了哪些事情
限于篇幅,分章节展示:

prepareRefresh----准备上下文
obtainFreshBeanFactory----获取bean工厂
parepareBeanFatory----配置bean工厂
postProcessBeanFactory-----对bean工厂进行后置处理
invokeBeanFactoryPostProcessors----执行bean工厂增强处理器
registerBeanPostProcessors----注册bean增强器
initMessageSource----初始化消息源
initApplicationEventMulticaster----初始化事件多播器
onRefresh()----刷新
registerListeners----注册监听器
finishBeanFactoryInitialization----初始化bean(重点

总结

以上我们分析了ioc容器的初始化过程,可以总结流程图如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以上都是比较具体的操作流程,然而此流程可抽象总结如下:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值