推荐:体系化学习Java(Java面试专题)
谈谈对IOC的理解?
怎么回答?先从哪些方面说?
1、概念,怎么理解这个概念呢?
2、干什么的?或者说解决了我们什么问题?
3、应用?
4、核心原理?
5、学习IOC的目的?
1、概念,怎么理解这个概念呢?
IOC (Inversion of control )术语 控制反转,怎么理解这四个字呢?
如果没有框架,那么我们自己要创建一个对象并且调用,就得自己去写个单例模式,初始化一个对象。然后通过set方法、或者构造器等方式注入到其他的类中。现在有了框架呢?我们就不需要自己动手了,把所有bean对象的创建都交给IOC去创建。所以大家又把IOC说成bean的一个容器。
2、干什么的?或者说解决了我们什么问题?
作用其实这个在上面其实已经说了,IOC主要是帮我们解决了bean的创建。但是不仅仅如此,为什么这么说呢?一个创建bean的容器,大家仔细思考应该也能实现,通过反射的机制就可以实现。这只能算是简单的实现,真正的框架它为什么要设计的复杂而不是我们想的那样,三言两语就可以搞定呢?更多的考虑是扩展性,扩展性是一个框架的生命力。也就是说IOC不只是创建了bean,还提供了一系列的扩展性能力。这些扩展性能够让我们在bean的生命周期中加入我们自定义的内容。这也就是为什么我们说AOP是IOC的一个扩展点。
3、应用?
1、通过想XML配置文件去声明一个bean
2、通过注解去声明一个bean
IOC容器会根据xml、注解去帮我们创建bean。
4、核心原理?
IOC创建bean的核心原理是什么呢?要想知道核心原理当然要去看源码?
如何看源码呢?先说下阅读源码的思路?
要先了解或者猜测或者说分析出bean的大致创建流程,带着流程去一步步看每一块的细节实现。
那么就先做第一步猜想?我习惯用的猜想方式就是,如果我来实现这么一个东西,我是怎么做的?
用容器创建bean的核心是什么?是动态创建,不是我们自己一个个new,既然不是new,那么java还有什么方式可以创建出对象呢?
当然是反射了,java提供了反射的机制,才能实现对象的动态创建。既然有了核心的能力,那么我们是不是需要定义用户声明需要创建哪些bean的入口或者说方式吧。
我们能想到什么方式?JSON?XML?YAML?
当然这些方式都行,无非是一个配置化的东西,我们定义好规范。
例如xml格式的:
定义bean的声明就要用标签,然后还要定义什么? 类的包名啊,如果不提供完全限定名(包+类),那么是不是不同包名下的同名类,都共用一个bean对象了,那不是乱套了吗?除了这些还要什么呢?
我们就结合我们创建一个对象分析,我们创建一个对象,都是new,但是new又分两种,第一种直接使用默认无参构造器new,第二种使用有参构造器new。
也就是说我们上面只是实现了无参的对象创建,那么如果要实现有参的对象创建呢?
我们及再定义一个规范,在标签里面加上子标签,然后把参数名和值声明好,这种就类似key-value的设计就行了。
如果我想创建的时候调用一个指定的方法呢?是不是还得加上一个init-method的配置,指定初始化调用这个方法。但是如果想初始化调用很多方法呢? 这个没必要了,我只需要给你一个调用入口,你如果要多个方法调用,就在全部整合到入口方法中就行了。
定义好了xml里面bean的声明规范,下一步我们改怎么办,当然是xml放在哪里呢? 因为我们要去读它,然后解析出需要创建的bean。
硬编码方式,规定好指定的目录,指定必须是什么名称的xml。显然这种方式不可取,耦合性太强,不符合我们的设计规范。那么我们就分析,动态配置读取xml,是不是还得有个地方指定我们去读什么路径下的xml。有什么方式实现呢?
1、注解。
2、硬编码方式,指定一个固定路径固定名称的xml,这个里面配置需要读取的配置问题。这种方式相当于给了一个入口配置文件,如果后面有很多的配置问题,都可以以这个文件为入口。
3、要么就写个配置类。
这几种都是可取的。如果在框架的实现上,我们可以让这几种方式并存。
定义到配置的xml和读取方式,那么我们就可以构建流程了。
1、配置xml文件,声明需要创建的bean
2、配置读取xml入口,例如选择固定的xml。
3、我们得设计一个类去解析xml了吧,然后解析到需要创建的bean,可能配置文件里面配置了很多。这时候我们就根据声明的bean创建一个集合存储需要创建的bean。
4、获取bean的集合之后,下一步是不是改遍历逐一去创建bean。创建就通过反射去创建。
5、我们需要创建一个集合,比如说用Map集合,将创建好的bean放进去存储,key开始bean的名称,value就是bean的对象。这样我们不仅能够保证创建的对象是单例的,在调用的时候,还能快速拿出对象。
6、bean创建完成了,就该调用初始化的方法
这样一个简单的IOC容器是不是就完成了?
下一步是不是需要稍微优化一下?方便开发者获取bean
集合一些设计模式,例如我们容器的核心创建逻辑,是不是要对用户透明化?因为用户既然将bean交给容器创建了,那么bean的生命周期什么的当然就交给容器去管理了,我作为开发者不需要了解,我只需要在我业务侧需要使用的时候,你能给我一个对象,至于对象是单例,还是现场去new的,跟我没关系,性能好不好那是你应该考虑的问题,我只需要做好我的业务实现。这样我们就能将业务和bean的创建解耦,并且我们要将实现逻辑隐藏起来,或者说保护起来,不然开发者随便修改,如果修改了那影响了我核心的逻辑怎么办呢?
所以我需要定义一个统一个入口类,这个类可以干什么呢?
帮助开发者获得bean。当然做的更好一点的,就多写一点,能够获得bean的其他相关内容,例如:bean是不是单例的?bean的类型?
那么这个入口我们可以怎么定义呢?用工厂的设计模式,定义一个上层的工厂接口BeanFactory,以后什么通过xml方式的,通过yaml方式的,通过json方式的,都要实现我这个接口,从这个接口中获取bean的。
优化好了之后,我们发现一个问题,我隐藏核心逻辑,不让开发者修改,那么如果开发者需要对bean修改呢?在bean的创建过程中需要加入自己的处理逻辑呢?怎么弄呢?并且我们这个容器是整个框架的基石,如果后面有2.0、3.0版本呢?在上面需要衍生更多的东西呢?难道每次版本迭代我都把之前的东西推翻,重新搞一套?所以到了这个实现,我们最重要的是向开发者提供额外的扩展入口。
那就想想哪些地方可以让我们加入扩展的口子?我们的目标是bean,那么扩展也是围绕这bean的生命周期。
那就得先想想bean经历了哪些流程?
1、bean经历了从xml中读取,放到一个声明集合里(beanDefinitionMap 在DefaultListableBeanFactory里)
2、经历了从声明集合中遍历出来创建,但是创建的过程分为实例化和初始化两个过程,这个如果了解对象的创建都清楚。第一个步骤实例化对象,分配默认的内存空间,第二步实例化,做属性填充。
3、完成bean的初始化,开始初始方法的调用
4、bean的随着容器的关闭而销毁。
大致是以上四个步骤,这四个步骤是不是每一步之间我都可以提供扩展入口。
bean读取和放到声明集合之间,我是不是可以加上一个读的扩展入口?
bean放入声明集合之后到初始化之前,我是不是可以加入一个扩展入口?
bean实例化和初始化,也就是属性填充之前?我是不是可以加入一个扩展入口?
bean的初始化之后,方法调用之前,我是不是可以加入一个扩展入口?
bean的方法调用之后,我是不是可以加入一个扩展入口?
bean的销毁之前,我是不是可以加入一个扩展入口?
bean的销毁之后,我是不是可以加入一个扩展入口?
所以这些地方可以设置很多扩展点。以后开发者就只需要继承或者实现我的扩展入口就可以完成在bean的生命周期中加入自定义的逻辑。但是我又规定好了扩展方法,你只能按我提供给你方法去扩展,这样又可以规避掉任意改动影响核心逻辑的风险。
按这这个猜想思路去读源码,看看具体的实现细节就行了。当然实际的IOC容器远比我们猜想的要复杂和严谨。
我就列出几个关键的点:
1、装载xml配置文件或注解的入口类分别是:ClassPathXmlApplicationContext、AnnotationConfigApplicationContext,都是从AbstractApplicationContext继承过来。ClassPathXmlApplicationContext、AnnotationConfigApplicationContext的核心入口方法是AbstractApplicationContext.refresh();
2、读取xml或者其他格式的配置文件,上层接口BeanDefinitionReader,注解的上层读取的接口是AnnotatedBeanDefinitionReader。
3、bean被读取之后,转成了BeanDefinition接口,也就是bean的声明或者定义。它是被包装在DefaultListableBeanFactory的成员变量Map<String, BeanDefinition> beanDefinitionMap里。
也就是说这个过程,不仅把配置的bean处理成了BeanDefinition,还创建了BeanFactory的对象。
4、实例化调用BeanFactoryPostProcessor。这个增强器有一个处理方法,这个就是扩展入口,我们可以实现这个增强器,重现方法,加入我们的处理逻辑。
5、注册BeanPostProcessor,这个接口有两个方法,一个前置处理和一个后置处理,分别在方法调用的前后执行。
6、bean实例化之前,会对设置一些Aware的接口,这个接口中没有任何的方法,就是一个规范,一个标记,继承了这个接口的接口只有一个抽象方法,都是void setXXX(String xxx)格式,这个是接口规范规定的。它的作用是代表继承这个接口,bean有能力可以获得容器的服务。通过不同的子实现,获得不同的容器能力。在BeanFactory的注释中也讲到,BeanFactory尽可能的提供Bean的生命周期接口。也提供了一个完整的初始化方法和顺序。
BeanNameAware 设置bean名称
BeanClassLoaderAware 设置bean类加载器
BeanFactoryAware 设置bean工厂
EnvironmentAware 环境
EmbeddedValueResolverAware 值解析器
ResourceLoaderAware
ApplicationEventPublisherAware
MessageSourceAware
ApplicationContextAware
ServletContextAware
7、设置好Aware,然后就是属性填充,关键方法populateBean()
getBean - doGetBean - createBean - doCreateBean - createBeanInstance - populateBean - initializeBean - 前置通知调用 - invokeInitMethods - 后置通知调用
8、BeanPostProcessor的前置通知调用 postProcessBeforeInitialization 和后置通知调用postProcessAfterInitialization
9、方法调用 invokeInitMethods()
5、学习IOC的目的?
不单单是为了学习怎么使用,更多的是学习他的如何设计的,如何扩展的。