简介: 项目的初衷是独立作出一个成熟的有特色的IOC容器,但由于过程参考Spring太多,而且也无法作出太多改进,于是目的变为以此项目作为理解Spring的一个跳板,与网上的一些模仿Spring的框架不同,本项目主要是针对注解形式
概述
项目的初衷是独立作出一个成熟的有特色的IOC容器,但由于过程参考Spring太多,而且也无法作出太多改进,于是目的变为以此项目作为理解Spring的一个跳板,与网上的一些模仿Spring的框架不同,本项目主要是针对注解形式
地址是Thales
流程
在Spring中,一个bean的形成分三个大的阶段,
- bean的定义阶段(包含BeanDefinition的加载,解析,与注册)
- bean的实例化阶段(包含对象的创建,属性的注入)
- bean的初始化阶段(包含一些资源的初始化,譬如打开文件建立连接等等)
这只是大概的划分,关于BeanPostProcessor等后置处理并没有显式的提及.
类的设计
如果只想了解一个bean是怎么从生到死的,只需要一步步debug就好了,如果看不懂,就多debug几遍.可是如果想实现一个类似的容器,类的设计,职责的分配,接口的实现继承必然是要了解的(除非你想几个类做完所有的事)
以下是DefaultListableBeanFactory
的类图
我们再来看一张图
我们再来看一套对比图
说这么多的目的,是说明我们没必要一开始就奔着最完善的目标去写,可以一步步来,一步步加入功能
实现简易IOC
众所周知,SpringIoC中最基本的就是BeanFactory
我们先定义一个BeanFactory接口
beanDefinition
由于是注解形式,我们不能再像xml那样给定一个资源文件再去解析了,而应该去扫描classPath下所有带有@Component
的类,这时候我们需要给定的参数就从文件路径变成了包路径,我们只需要扫描这个包及其子包内符合条件的类,并且将其转化为BeanDefinition再注册就好.执行这个功能的是
ClassPathBeanDefinitionScanner
这个类.在这一步,就已经和传统的流程有所区别了,我们会传入一个ResourceLoader去实现具体的扫描功能(即定位),但不会再有专门的类去处理解析这一步
但最后直接使用的并不是PathMatchingResourcePatternResolver
而是把他作为ClassPathBeanDefinitionScanner
的一个属性,在这个类里调用.
我们得到了Resource,如何获得对应的BeanDefinition
?
先考虑这样一个问题,什么样的类可以被注册BeanDefinition
?
- 添加了@Component注解或者满足其他注册的条件
- 不是接口或者抽象类
所以我们可以单独抽象出一个方法 boolean isCandidateComponent(Class<?> clazz)
来判断是否被注册
现在到了注册阶段,依旧秉持面向接口编程的理念,同时考虑到单一职责,我们把注册Bean定义单独抽象出来
上文说到Bean定义的定位,解析,注册都是在ClassPathBeanDefinitionScanner
里完成的,于是BeanDefinitionRegistry
自然也成为了ClassPathBeanDefinitionScanner
的属性之一
于是ClassPathBeanDefinitionScanner
构建完成了
实例化
在什么时候实例化?我们说,在调用getBean()而又没有现成的bean时进行实例化
对象创建
有两种方式,通过Jdk默认的反射实现,或者用cglib代理实现.
默认自然是无参构造,但是如果传入了参数,则需要根据参数的类型和数量去匹配对应的构造函数,用其去实例化
于是我们抽象出InstantiationStrategy
作为实例化接口,两种实例化方法都需要实现这个接口,我们真正去用的时候只需要去调该接口的方法就好
属性注入
字段值获取
有两种方式可以实现字段值获取
- 直接注解Autowired或者Value
- Value里面填的不是值而是占位符,那么就需要解析占位符去获取
我们通过Class对象获取所有字段,再通过遍历所有字段查找加在字段上的注解来获取(这仅仅只是Spring的一种注入方式)
字段值填充
获取字段值后通过反射填入相应的字段中
初始化
调用指定的初始化方法,进行资源的初始化.,如何获取初始化方法?在xml模式中,只要加个标签即可,如果是注解模式,加个注解标识一下或者在某个注解上加个参数,代表初始化方法,这个还没有实现
功能填充
后置处理器添加
上面我们已经实现了一个可以进行依赖查找,依赖注入的Bean容器,让我们再回顾一下Spring的流程,我们少了些什么,最容易想到的应该就是后置处理器了,包括BeanFactoryPostProcessor
和BeanPostProcessor
两种,前者对于beanFactory
进行修改操作,后者对于bean
进行修改操作,同样是面向接口编程
首先建立BeanPostProcessor
就目前来看,有什么是需要BeanPostProcessor
来做的呢?我们可以把之前对注解进行处理,获取注入属性的代码分离出来,专门用一个BeanPostProcessor
去处理
所有自定义实现的BeanPostProcessor
都需要继承这个接口,由于BeanPostProcessor
的作用是处理其他的Bean
,所以必须要在其他被处理的Bean实例化之前被创建出来.于是我们在finishBeanFactoryInitialization(beanFactory);
之前添加registerBeanPostProcessors(beanFactory);
用于实例化所有的BeanPostProcessor
而这些beanPostProcessor
的重要程度是不同的,例如处理注解注入的BeanPostProcessor
优先级就要比一般的BeanPostProcessor
优先级要高,所以需要先实例化
Aware接口添加
其实现在我们已经可以完全的把一个对象交由IOC容器了,但此时这个对象与容器之间的关系是单向的,容器能够操作bean
,但bean
不能借助容器,为了解决此类问题,我们添加一个Aware接口作为标志接口,由各个更具体的Aware去继承他,并在实例化属性之后,初始化方法执行之完成相关容器属性的注入
事件监听器添加
监听器是观察者模式的一种实现
我们先定义以下几个基本接口
具体调用流程为具体的listener
被添加到广播器中,事件通过publisher
统一发布,而publishEvent
最后会调用 multicastEvent(ApplicationEvent event)
方法,经过相应判断后由对应监听器做出相应操作.
如何判断这个监听器是否对该事件感兴趣?
我们事先实现的listener
是有泛型的,我们可以通过这个泛型与传入的事件类型的关系来判断
FactoryBean添加
目前的Bean
都是由BeanFactory
来产生的,
我们用FactoryBean接口来标识这个产生Bean的特殊的Bean
循环依赖的解决
循环依赖是指A依赖于B的同时B依赖于A,解决方法为实例化与初始化分离,如果只考虑一般情况的话用两级缓存实际上就够了,
代码优化
实现简易AOP
如果从正统的AOP开始的话,随之而来的就是一堆概念,包括切点,通知一类
我们先看AOP要做什么
所以说AOP的核心就是动态代理,我们以Cglib为例来看看动态代理要怎么用
这就是动态代理最核心的功能,也是AOP的核心功能,AOP的最终目的是代码5,即产生一个代理对象,把这个代理对象交给IOC去管理
而为了达成这个目的,AOP框架需要做好代码1-4所需要做的事,一和二组合起来,成了JoinPoint
,3叫做Advice
,这两个组合起来就叫做Advisor
,可不可以不分这些种类,就全写在一个或几个类里,当然可以,Spring0.9
就是这么做的,但发展到如今,早已采用了这种划分方式.本项目也采用这种分类.
先从连接点说起,如何确定到底在哪里实现功能增强,无非是类与方法两个层次;
我们先定义ClassFilter
与MethodMacther
两个接口
这两个接口必然是组合起来使用的,于是我们用PointCut
将其组合起来
接口只是定义了抽象功能,这些功能还要有具体的实现
我们默认用Java的正则去匹配方法名,以此构建出JdkRegexMethodMatcher
在Spring
中,并不是直接继承的MethodMatcher
,考虑到正则的语法不同,额外做了一层抽象,但在此处省略掉了
而JdkRegexMethodMatcher
同时也实现了PointCut
类,也就是说,现在切点已经准备好了
再来看Advice
由于考虑的可扩展点比较多,于是继承的层次也变的多了
现在Advice
也定义完了,具体的实现我们交由用户去做
接下来就是整合成Advisor
了
目前已经定义好了Advisor
的功能
我们再实现这个接口
RegexMethodPointcutAdvisor
就整合了PointCut
以及Advice
,通过他,我们就可以确定在何处做何种增强.
现在的advisor
可以完成检验一个类是否要被代理的功能,但是如果这个类需要被代理,advisor
却无法保存这个类的对应信息
于是我们需要一个类将advisor
与对应的代理类结合起来,这就是AdvisedSupport
上类属性中的TargetSource
便是真正持有代理对象信息的类
现在万事具备,只需要用Cglib
去使用我们已经持有的信息就可以创建出新的类了
将这份代码与最初使用cglib
的代码比较,会发现过程几乎是一模一样.但是作为一个框架,应该尽可能的给用户以方便
于是我们需要一个Creator
去把这一切都做好,他需要负责将Advice
和PointCut
组合成Advisor
,再将Advisor
与TargetSource
组装成AdvisedSupport
,再将AdvisedSupport
交给Cglib
动态代理,产生代理对象,而用户只需要编写Advice
以及切入点表达式即可
功能演示
-
属性注入
- 基本类型
- 引用类型
- 循环依赖
- 容器感知
- FactoryBean生成对象
- AOP切面增强
- 自定义
BeanPostProcessor
困难及解决
- 首先是设计上的问题
- FactoryBean的实现
- AOP与IOC的结合
- 字段的注入
本文为阿里云原创内容,未经允许不得转载。