概述
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容器的初始化过程,可以总结流程图如下:
以上都是比较具体的操作流程,然而此流程可抽象总结如下: