一、IOC分析
1. IOC是什么?
IOC:Inversion of Control控制反转,也称依赖倒置(反转)
问题:如何理解控制反转?
反转:依赖对象的获得被反转了。由自己创建,反转为从IOC容器中获取(和自动注入)
2. IOC容器带来什么好处?
1)代码更简洁,不需要去new需要使用的对象了。
2)面向接口编程,使用者与具体类解耦,易扩展、替换实现者。
3)可以方便进行AOP增强。进行AOP的前提是有IOC
3. IOC容器做什么工作?
IOC容器的工作:负责创建、管理类实例,向使用者提供实例。
4. IOC容器是否是工厂模式的实例?
是的。IOC容器负责来创建类实例对象,使用者需要实例就从IOC容器中get。也称IOC容器为bean工厂。
二、IOC设计实现
1. IOC容器的工作
创建、管理bean。它是一个工厂,负责对外提供bean实例。
问:bean是什么?
bean是组件,就是类对象!
1)IOC容器应该具备什么行为(对外接口)?
对外提供bean实例,getBean()方法
2) 这个getBean()方法是否需要参数?需要几个参数、什么类型的参数?
简单工厂模式中,当工厂能创建很多类产品时,如要创建某类产品,需要告诉工厂。
3)这个getBean()方法的返回值应是什么类型?
各种类型的bean,那就只能是Object了。
经过上面的问题Bean工厂的接口就可以定义出来了!!!
2. Bean工厂接口:
Bean工厂怎么知道该如何创建bean?
如何告诉Bean工厂?
就是一个定义注册,我们可以给它定义一个bean定义注册接口
3. Bean定义注册接口
4. Bean定义
问题1:bean定义的用途是什么?
告诉Bean工厂该如何创建某类bean
问题2:获得类的实例的方式有哪些?
new 构造方法
Person p = new Person();
工厂方法
静态的
public class PersonFactory {
public static Person getPerson() {
return new Person();
}
}
成员方法
public class PersonFactory {
public Person getPerson() {
return new Person();
}
}
问题3:Bean工厂帮我们创建bean时,它需要获知哪些信息?
1)new 构造方法的方式创建bean时,Bean工厂需要知道要创建的类的类名
2)静态工厂方法的方式创建bean时,Bean工厂需要知道工厂类名、工厂方法名
3)成员工厂方法的方式创建bean时,Bean工厂需要知道工厂类名(工厂bean名)、工厂方法名
因为需要获取工厂类对象去调用工厂方法名创建bean,所以直接给工厂bean名先创建工厂bean对象
问题4:每次从Bean工厂获取bean实例时,是否都需要创建一个新的?
否,有的只需要单实例
问题5:Bean定义是给Bean工厂创建bean用的,那么Bean定义接口应向Bean工厂提供哪些方法?
new 构造方法的方式创建bean时,需要告诉bean工厂怎么获取类的名称——获取bean的类名:getBeanClass (): Class
静态工厂方法的方式创建bean时,需要告诉bean工厂怎么获取工厂方法名:getFactoryMethodName() : String
成员工厂方法的方式创建bean时,需要告诉bean工厂怎么获取工厂bean名:getFactoryBeanName() : String
是否是单例等方法:getScope() : Sting、isSingleton()、isPrototype()
问题6:类对象交给IOC容器来管理,类对象的生命周期中还可能有什么生命阶段事情要做吗?
比如创建对象后可能需要进行一些初始化
还有一些对象在销毁时可能要进行一些特定的销毁逻辑(如释放资源)
那就在Bean定义中提供让用户可指定初始化、销毁方法。
对Bean工厂就需提供getInitMethodName()、getDestroyMethodName()
5. Bean定义接口
我们继续看下面的图:
说明:bean定义BeanDefinition通过bean定义注册接口BeanDefinitionRegistry注册到Bean工厂BeanFactory,Bean工厂BeanFactory负责创建bean
6. BeanFactory实现
实现一个最基础的默认bean工厂:DefaultBeanFactory
说明:
6.1 实现bean定义信息注册接口
问题1:bean定义信息如何存放?
Map
问题2:bean定义是否可以重名?重名怎么办?
重名抛异常
6.2 实现bean工厂
问题1:创建的bean用什么存放,方便下次获取?
Map,因为getBean是通过名字来取的,放在Map中更好
问题2:在getBean方法中要做哪些事?
创建bean实例,进行初始化
6.4 扩展DefaultBeanFactory
对于单例bean,我们可以提前实例化,这样做的好处是不用在需要的时候再取获取了,可以保证线程安全,提高性能
三、DI分析
DI(Dependency Injection)依赖注入分析
问题1:哪些地方会有依赖?
构造参数依赖
属性依赖
问题2:依赖注入的本质是什么?
赋值,给入构造参数值,给属性赋值
问题3:参数值、属性值可能是什么值?
直接值、bean依赖
举例:
public class Girl{
public Girl(String name,int age,char cup,Boy boyfriend){
}
}
name,age,cup都是直接值,boyfriend是bean依赖
问题4:直接值会有哪几种情况?
基本数据类型、String
数组、集合
Properties
Map
本质:参数值、属性值都是值。bean工厂在进行依赖注入时,就是给入值。
四、DI实现
1. 构造参数依赖
1.1 构造参数依赖定义分析
public class Girl{
public Girl(String name,int age,char cup,Boy boyfriend){
}
}
问题1:我们要创建一个Girl是如何创建的?
Boy leeSmall = new Boy("leeSmall");
Girl girl = new Girl("小青",18,'D',leeSmall);
直接把值传入构造函数即可
问题2:我们可不可以这样来定义构造参数依赖?
第一个参数值是:“小青”
第二个参数值是:18
第三个参数值是:‘D’
第四个参数值是:依赖一个Boy Bean
完全可以!
构造参数依赖设计
问题1:参数可以有多个,用什么存储?
集合:List
问题2:参数有顺序,如何体现顺序?
按参数顺序放入List
问题3:参数可以值直接值,也可以是bean依赖,如何表示?
因为可以有多种值,那就只能用Object
List constructorArgumentValues
问题4:如果用Object来表示值,如何区分是Bean依赖?
为Bean依赖定义一种数据类型BeanReference,bean工厂在构造Bean实例时,遍历判断参数是否是BeanReference,如果是则替换为依赖的bean实例。
问题5:如果直接值是数组、集合等,它们的元素中有的是bean依赖,怎么处理?
元素值还是用BeanReference,同样bean工厂在使用时需遍历替换。
1.2 BeanReference
BeanReference就是用来说明bean依赖的:依赖哪个Bean
1.3 在BeanDefinition中增加获得构造参数值的方法
构造参数依赖有了,下面就可以来实现构造参数依赖注入了!
1.4 BeanFactory中实现构造参数依赖注入
1.4.1 首先需要把bean定义中的构造参数引用转为真实的值,在DefaultBeanFactory中增加一个方法来干这事。
问题:有参数了,如何断定是哪个构造方法、哪个工厂方法?需要考虑下面的情况
a)方法是可以重载的
b)形参定义时可能是接口或者父类,实参则是具体的子实现
c)可以通过反射获取提供的构造方法、方法,如下:
java.lang.Class
判断逻辑:
a)先根据参数的类型进行精确匹配查找,如未找到,则进行第二步查找;
b)获得所有的构造方法遍历,通过参数数量过滤,再比对形参类型与实参类型
1.4.2 当我们判断出构造方法或者工厂方法后,对于原型Bean,下次获取是否就可以省去判断了?
也就是说,对于原型Bean,我们可以缓存下这个构造方法或工厂方法。如何实现?
1.4.3 接下来可以写查找构造方法或查找工厂方法的代码了
1)在DefaultBeanFactory中增加查找构造方法的方法
2)修改DefaultBeanFactory中用构造方法创建实例的代码调用determineConstructor
3)按照增加查找构造方法的方式修改静态工厂方法、工厂方法方式的参数依赖
修改DefaultBeanFactory中用工厂方法创建实例的代码调用determineFactoryMethod
1.4.5 循环依赖如何处理
问题:构造对象时可以循环依赖吗/
构造实例对象时的循环依赖,会陷入僵死局面,是不允许构造实例时的循环依赖的。那么怎么发现循环依赖呢?
发现循环依赖的方法:加入一个正在构造的bean的记录,每个bean开始构造时加入该记录中,构造完成后从记录中移除。如果有依赖,先看依赖的bean是否在构造中,如是就构成了循环依赖,抛出异常
2. 属性依赖
问题1:属性依赖是什么?
某个属性依赖某个值,需要外部给入这个值
问题2:该如何来描述一个属性依赖?
属性名、值,定义一个来来表示这两个值
问题3:会有多个属性依赖,怎么存放?
List存放
问题4:属性值得情况和构造参数值得情况一样吗?
一样
2.1 定义属性依赖实体
属性依赖实体类类图如下:
2.2 在BeanDefinition接口中增加获得属性依赖定义的方法
2.3 在GenericBeanDefinition增加对应的实现
2.4 在DefaultBeanFactory中实现属性依赖
在doGetBean(String beanName)中增加对设置属性依赖的调用 放在方法最后可行
描述一个属性依赖?**
属性名、值,定义一个来来表示这两个值
问题3:会有多个属性依赖,怎么存放?
List存放
问题4:属性值得情况和构造参数值得情况一样吗?
一样
2.1 定义属性依赖实体
属性依赖实体类类图如下:
[外链图片转存中…(img-T6AgK1bl-1586693628403)]
2.2 在BeanDefinition接口中增加获得属性依赖定义的方法
2.3 在GenericBeanDefinition增加对应的实现
2.4 在DefaultBeanFactory中实现属性依赖
在doGetBean(String beanName)中增加对设置属性依赖的调用 放在方法最后可行
注意:属性依赖是允许循环依赖的,因为是在实例创建好之后才设置属性依赖的值的!!!