什么是IOC容器设计理念以及源码怎么写
什么是IOC容器设计理念以及源码怎么写,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
⼀、IOC核⼼理论回顾
知识点:
1. Ioc理念概要
2. 实体Bean的创建
3. Bean的基本特性
4. 依赖注⼊
1、Ioc理论概要
在JAVA的世界中,⼀个对象A怎么才能调⽤对象B?通常有以下⼏种⽅法。
类别描述时间点
外部传⼊
构造⽅法传⼊创建引⽤对象时
属性设置传⼊设置对象状态时
运⾏时做为参数传⼊调⽤时
内部创建
属性中直接创建创建引⽤对象时
初始化⽅法创建创建引⽤对象时
运⾏时动态创建调⽤时
上表可以看到, 引⽤⼀个对象可以在不同地点(其它引⽤者)、不同时间由不同的⽅法完
成。如果B只是⼀个⾮常简单的对象 如直接new B(),怎样都不会觉得复杂,⽐如你从来不会
觉得创建⼀个St ring 是⼀个件复杂的事情。但如果B 是⼀个有着复杂依赖的Service对象,这
时在不同时机引⽤B将会变得很复杂。
⽆时⽆刻都要维护B的复杂依赖关系,试想B对象如果项⽬中有上百过,系统复杂度将会成陪
数增加。
IOC容器的出现正是为解决这⼀问题,其可以将对象的构建⽅式统⼀,并且⾃动维护对象的依
赖关系,从⽽降低系统的实现成本。前提是需要提前对⽬标对象基于XML进⾏声明。
2、实体Bean的构建
a. 基于Class构建
b. 构造⽅法构建
c. 静态⼯⼚⽅法创建
d. Fact oryBean创建
1、基于ClassName构建
这是最常规的⽅法,其原理是在spring底层会基于class 属性 通过反射进⾏构建。
2、构造⽅法构建
如果需要基于参数进⾏构建,就采⽤构造⽅法构建,其对应属性如下:
name:构造⽅法参数变量名称
t ype:参数类型
index:参数索引,从0开始
value:参数值,spring 会⾃动转换成参数实际类型值
ref :引⽤容串的其它对象
3、静态⼯⼚⽅法创建
1
2
3
4
1
2
3
如果你正在对⼀个对象进⾏A/B测试 ,就可以采⽤静态⼯⼚⽅法的⽅式创建,其于策略
创建不同的对像或填充不同的属性。
该模式下必须创建⼀个静态⼯⼚⽅法,并且⽅法返回该实例,spring 会调⽤该静态⽅法
创建对象。
4、Fact oryBean创建
指定⼀个Bean⼯⼚来创建对象,对象构建初始化 完全交给该⼯⼚来实现。配置Bean时指定该
⼯⼚类的类名。
3、bean的基本特性
???? 作⽤范围
???? ⽣命周期
???? 装载机制
public static HelloSpring build(1 String type) {
2 if (type.equals("A")) {
3 return new HelloSpring("luban", "man");
4 } else if (type.equals("B")) {
5 return new HelloSpring("diaocan", "woman");
6 } else {
7 throw new IllegalArgumentException("type must A or B");
8 }
9 }
1
2
3
1 public class DriverFactoryBean implements FactoryBean {
2 private String jdbcUrl;
3 public Object getObject() throws Exception {
4 return DriverManager.getDriver(jdbcUrl);
5 }
6 public Class> getObjectType() {
7 return Driver.class;
8 }
9 public boolean isSingleton() {
10 return true;
11 }
12 public String getJdbcUrl() {
13 return jdbcUrl;
14 }
15 public void setJdbcUrl(String jdbcUrl) {
16 this.jdbcUrl = jdbcUrl;
17 }
18 }
a、作⽤范围
很多时候Bean对象是⽆状态的 ,⽽有些⼜是有状态的 ⽆状态的对象我们采⽤单例即可,⽽有
状态则必须是多例的模式,通过scope 即可创建
scope="prot ot ype"
scope="singlet on"
如果⼀个Bean设置成 prot ot ype 我们可以 通过BeanFact oryAware 获取 BeanFact ory 对象
即可每次获取的都是新对像。
b、⽣命周期
Bean对象的创建、初始化、销毁即是Bean的⽣命周期。通过 init -met hod、dest roymet
hod 属性可以分别指定期构建⽅法与初始⽅法。
如果觉得麻烦,可以让Bean去实现 Init ializingBean.af t erPropert iesSet ()、
DisposableBean.dest roy()⽅法。分别对应 初始和销毁⽅法
c、加载机制
指示Bean在何时进⾏加载。设置lazy-init 即可,其值如下:
t rue: 懒加载,即延迟加载
f alse:⾮懒加载,容器启动时即创建对象
def ault :默认,采⽤def ault -lazy-init 中指定值,如果def ault -lazy-init 没指定就是
f alse
什么时候使⽤懒加载?
懒加载会容器启动的更快,⽽⾮懒加载可以容器启动时更快的发现程序当中的错误 ,选择哪
⼀个就看追求的是启动速度,还是希望更早的发现错误,⼀般我们会选 择后者。
4、依赖注⼊
试想IOC中如果没有依赖注⼊,那这个框架就只能帮助我们构建⼀些简单的Bean,⽽之前所说
的复杂Bean的构建问题将⽆法解决,spring这个框架不可能会像现在这样成功。 spring 中
ioc 如何依赖注⼊呢。有以下⼏种⽅式:
1. set⽅法注⼊
2. 构造⽅法注⼊
3. ⾃动注⼊(byName、byT ype)
4. ⽅法注⼊(lookup-met hod)
1、set⽅法注⼊
1 scope="prototype
2
3
1
1
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
3
4 default-lazy-init="true">
2、构造⽅法注⼊
3、⾃动注⼊(byName\byT ype\const ruct or)
byName:基于变量名与bean 名称相同作为依据插⼊
byT ype:基于变量类别与bean 名称作
const ruct or:基于IOC中bean 与构造⽅法进⾏匹配(语义模糊,不推荐)
4、依赖⽅法注⼊(lookup-met hod)
当⼀个单例的Bean,依赖于⼀个多例的Bean,⽤常规⽅法只会被注⼊⼀次,如果每次都
想要获取⼀个全新实例就可以采⽤lookup-met hod ⽅法来实现。
该操作的原理是基于动态代理技术,重新⽣成⼀个继承⾄⽬标类,然后重写抽像⽅法到达注⼊
⽬的。
前⾯说所单例Bean依赖多例Bean这种情况也可以通过实现 Applicat ionCont ext Aware 、
BeanFact oryAware 接⼝来获取BeanFact ory 实例,从⽽可以直接调⽤get Bean⽅法获取新实
例,推荐使⽤该⽅法,相⽐lookup-met hod语义逻辑更清楚⼀些。
⼆、IOC 设计原理与实现
2
3
1
2
3
4
5
1
2
1 #编写一个抽像类
2 public abstract class MethodInject {
3 public void handlerRequest() {
4 // 通过对该抽像方法的调用获取最新实例
5 getFine();
6 }
7 # 编写一个抽像方法
8 public abstract FineSpring getFine();
9 }
10 // 设定抽像方法实现
11
12
13
知识点:
1、源码学习的⽬标
2、Bean的构建过程
3、BeanFact ory与Applicat ionCont ext区别
1、源码学习⽬标:
不要为了读书⽽读书,同样不要为了阅读源码⽽读源码。没有⽬的⼀头扎进源码的⿊森林
当中很快就迷路了。到时就不是我们读源码了,⽽是源码'毒'我们。毕竟⼀个框架是由专业团
队,历经N次版本迭代的产物,我们不能指望像读⼀本书的⽅式去阅读它。 所以必须在读源码
之前找到⽬标。是什么呢?
⼤家会想,读源码的⽬标不就是为了学习吗?这种⽬标太过抽像,⽬标⽆法验证。通常我
们会设定两类型⽬标:⼀种是对源码进⾏改造,⽐如添加修改某些功能,在实现这种⽬标的过
程当中⾃然就会慢慢熟悉了解该项⽬。但然这个难度较⼤,耗费的成本也⼤。另⼀个做法是
⾃⼰提出⼀些问题,阅读源码就是为这些问题寻找答案。以下就是我们要⼀起在源码中寻找答
案的问题:
1. Bean⼯⼚是如何⽣产Bean的?
2. Bean的依赖关系是由谁解来决的?
3. Bean⼯⼚和应⽤上⽂的区别?
2、Bean的构建过程
spring.xml ⽂件中保存了我们对Bean的描述配置,BeanFact ory 会读取这些配置然后⽣
成对应的Bean。这是我们对ioc 原理的⼀般理解。但在深⼊⼀些我们会有更多的问题?
1. 配置信息最后是谁JAVA中哪个对象承载的?
2. 这些承载对象是谁业读取XML⽂件并装载的?
3. 这些承载对象⼜是保存在哪⾥?
BeanDefinit ion (Bean定义)
ioc 实现中 我们在xml 中描述的Bean信息最后 都将保存⾄BeanDefinit ion (定义)对象中,
其中xml bean 与BeanDefinit ion 程⼀对⼀的关系。
由此可⻅,xml bean中设置的属性最后都会体现在BeanDefinit ion中。如:
演示查看 BeanDefinit ion 属性结构
BeanDefinit ionRegist ry(Bean注册器)
在上表中我们并没有看到 xml bean 中的 id 和name属性没有体现在定义中,原因是ID 其作
为当前Bean的存储key注册到了BeanDefinit ionRegist ry 注册器中。name 作为别名key 注册
到了 AliasRegist ry 注册中⼼。其最后都是指向其对应的BeanDefinit ion。
演示查看 BeanDefinit ionRegist ry属性结构
XML-bean BeanDefinit ion
class beanClassName
scope scope
lazy-init lazyInit
const ruct or-arg Const ruct orArgument
propert y MutablePropertyValues
f act ory-met hod f actoryMethodName
dest roy-met hod Abst ract BeanDefinit ion.dest royMet hodName
init -met hod Abst ract BeanDefinit ion.init Met hodName
aut owire Abst ract BeanDefinit ion.aut owireMode
id
name
????
????
BeanDefinit ionReader(Bean定义读取)
⾄此我们学习了 BeanDefinit ion 中存储了Xml Bean信息,⽽BeanDefinit ionRegist er 基于ID
和name 保存了Bean的定义。接下要学习的是从xml Bean到BeanDefinit ion 然后在注册⾄
BeanDefinit ionRegist er 整个过程。
上图中可以看出Bean的定义是由BeanDefinit ionReader 从xml 中读取配置并构建出
BeanDefinit ionReader,然后在基于别名注册到BeanDefinit ionRegist er中。
查看BeanDefinit ???? ionReader结构
⽅法说明:
???? loadBeanDefinit ions(Resource resource)
???? 基于资源装载Bean定义并注册⾄注册器
???? int loadBeanDefinit ions(St ring locat ion)
???? 基于资源路径装载Bean定义并注册⾄注册器
???? BeanDefinit ionRegist ry get Regist ry()
???? 获取注册器
???? ResourceLoader get ResourceLoader()
???? 获取资源装载器
基于示例演示BeanDefinit ionReader装载过程
Beanf act ory(bean ⼯⼚)
有了Bean的定义就相当于有了产品的配⽅,接下来就是要把这个配⽅送到⼯⼚进⾏⽣产了。
在ioc当中Bean的构建是由BeanFact ory 负责的。其结构如下:
????
1 //创建一个简单注册器
2 BeanDefinitionRegistry register = new SimpleBeanDefinitionRegistry();
3 //创建bean定义读取器
4 BeanDefinitionReader reader = new XmlBeanDefinitionReader(register);
5 // 创建资源读取器
6 DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
7 // 获取资源
8 Resource xmlResource = resourceLoader.getResource("spring.xml");
9 // 装载Bean的定义
10 reader.loadBeanDefinitions(xmlResource);
11 // 打印构建的Bean 名称
12 System.out.println(Arrays.toString(register.getBeanDefinitionNames());
⽅法说明:
???? get Bean(St ring)
???? 基于ID或name 获取⼀个Bean
????
???? 基于Bean的类别获取⼀个Bean(如果出现多个该类的实例,将会报错。但可以指定
primary="t rue" 调整优先级来解决该错误 )
???? Object get Bean(St ring name, Object ... args)
???? 基于名称获取⼀个Bean,并覆盖默认的构造参数
???? boolean isT ypeMat ch(St ring name, Class> t ypeT oMat ch)
???? 指定Bean与指定Class 是否匹配
`
以上⽅法中重点要关注get Bean,当⽤户调⽤get Bean的时候就会触发 Bean的创建动作,其
是如何创建的呢?
演示基???? 本BeanFactory获取⼀个Bean
1 #创建Bean堆栈
2 // 其反射实例化Bean
3 java.lang.reflect.Constructor.newInstance(Unknown Source:-1)
4 BeanUtils.instantiateClass()
5 //基于实例化策略 实例化Bean
6 SimpleInstantiationStrategy.instantiate()
7 AbstractAutowireCapableBeanFactory.instantiateBean()
8 // 执行Bean的实例化方法
9 AbstractAutowireCapableBeanFactory.createBeanInstance()
10 AbstractAutowireCapableBeanFactory.doCreateBean()
11 // 执行Bean的创建
12 AbstractAutowireCapableBeanFactory.createBean()
13 // 缓存中没有,调用指定Bean工厂创建Bean
14 AbstractBeanFactory$1.getObject()
15 // 从单例注册中心获取Bean缓存
16 DefaultSingletonBeanRegistry.getSingleton()
17 AbstractBeanFactory.doGetBean()
Bean创建时序图:
从调⽤过程可以总结出以下⼏点:
1. 调⽤BeanFact ory.get Bean() 会触发Bean的实例化。
2. Def ault Singlet onBeanRegist ry 中缓存了单例Bean
3. Bean的创建与初始化是由Abst ract Aut owireCapableBeanFact ory 完成的。
3、BeanFactory 与 ApplicationContext区别
BeanFact ory 看下去可以去做IOC当中的⼤部分事情,为什么还要去定义⼀个
Applicat ionCont ext 呢?
Applicat ionCont ext 结构图
从图中可以看到 Applicat ionCont ext 它由BeanFact ory接⼝派⽣⽽来,因⽽提供了
BeanFact ory所有的功能。除此之外cont ext包还提供了以下的功能:
1. MessageSource, 提供国际化的消息访问
2. 资源访问,如URL和⽂件
3. 事件传播,实现了Applicat ionList ener接⼝的bean
4. 载⼊多个(有继承关系)上下⽂ ,使得每⼀个上下⽂都专注于⼀个特定的层次,⽐如应⽤
的web层
总结回顾:
BeanDefinit ion
Def ault ResourceLoader
XmlBeanDefinit ionReader
BeanDefinit ionRegist ry
18 // 获取Bean
19 AbstractBeanFactory.getBean()
20 // 调用的客户类
21 com.tuling.spring.BeanFactoryExample.main()
BeanFact ory
Def ault List ableBeanFact ory
Aut owireCapableBeanFact ory
Abst ract Aut owireCapableBeanFact ory
Singlet onBeanRegist ry
Def ault Singlet onBeanRegist ry
看完上述内容,你们掌握什么是IOC容器设计理念以及源码怎么写的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!