tiny-spring analysis
Foreword
Spring encountered in reading the source code (part of dependency injection and aspect-oriented programming part) a lot of confusion, a large class file structure, the complicated method calls polymorphic achieve lumpy, and let yourself get stuck, confused.
Later, I noticed code4craft of tiny-spring project, implemented a miniature of Spring, provide basic support for IoC and AOP, the small but perfectly formed, cognitive Spring's clear a lot. This miniature frame structure including file names, method names are to achieve the reference Spring, Spring for the first reading of the learner, as an aid to study Spring should be able to benefit.
In the study of tiny-spring time, learned a lot, some analysis of this micro-framework written down, the wording may be a bit disorder.The paper is organized
- Achieve a first portion IoC container corresponding to the tiny-spring to 1 step- to step-5 section, five step realization of the basic IoC container support singleton type of the bean, including initialization, attributes injection, and dependency Bean injection, can be read configuration from XML, XML is no specific way to read in depth.
- AOP to achieve the second portion of the container corresponding to the tiny-spring of step-6 into the step-9 portion. step-10 does not support the analysis of cglib. This four step can be written using the syntax of AspectJ AOP, support interface agent. Taking into account only AspectJ syntax for implementing the
execution("***")
Resolution section, not the main content, you can use Java regular expression roughly completed, so there is no attention to these details.Bibliography
"Spring combat" "Spring Inside"
- tiny-spring analysis
- Achieve IoC containers
- AOP implementations
Why not support GitHub TOC
Achieve IoC containers
File Structure
Resource
To Resource
interface to the core emanating several classes, it is used to solve the problem content IoC container came from, that is, where the configuration file read, how to read the configuration file.
The class name | Explanation |
---|---|
Resource |
Interface that identifies an external resource. By getInputStream() acquiring resource method input stream. |
UrlResource |
Implement Resource resource interface, access to resources through URL. |
ResourceLoader |
Resource loading class. By getResource(String) acquiring a method Resouce object is to get Resouce the main pathway. |
Note: There are some problems in the design, ResourceLoader
direct return a UrlResource
better approach is to declare an ResourceLoader
interface and then implement a UrlResourceLoader
class for loading UrlResource
.
BeanDefinition
To BeanDefinition
class as the core emanating several classes, are used to solve Bean
specific definition of the problem, including Bean
what the name is, what its type is that it gives the property which reference or by value, that is, how in the IoC container definition of a Bean
, so that the problem may be generated IoC container according to an example of this definition.
The class name | Explanation |
---|---|
BeanDefinition |
This class holds the Bean definition. Including Bean name String beanClassName , type Class beanClass , properties PropertyValues propertyValues . Instance of a class may be generated depending on its type, may then be injected into the property. propertyValues Which contains one PropertyValue entry, each entry in key-value pairs String - Object respectively corresponding to the names and types of instance attribute to be generated. In the Spring of the XML property , the key is key , the value is value or ref . For value as long as the property is injected directly into the line, but ref must first be resolved. Object If BeanReference the type, then it is a reference, which stores the name referenced need to be parsed, converted to the corresponding actual Object . |
BeanDefinitionReader |
Parsing BeanDefinition interface. By loadBeanDefinitions(String) loading a class definition from address. |
AbstractBeanDefinitionReader |
Implement BeanDefinitionReader the abstract class interface (not embodied loadBeanDefinitions , but the specification of the BeanDefinitionReader basic structure). A built-in HashMap rigistry , to save String - beanDefinition the pairs. A built ResourceLoader resourceLoader for holding class loader. The intention is that, when used, its only loadBeanDefinitions() pass a resource address, you can automatically call its class loader, and the resolve to BeanDefinition save to registry go. |
XmlBeanDefinitionReader |
A specific implementation loadBeanDefinitions() method, the class definition read from the XML file. |
BeanFactory
To BeanFactory
interface the core emanating several classes, are for solving the IoC container has been acquired in Bean
the case definition, how to assemble the acquired Bean
instance problem.
The class name | Explanation |
---|---|
BeanFactory |
Interfaces, identifying a IoC container. By getBean(String) acquiring an object method |
AbstractBeanFactory |
BeanFactory An abstract class that implements the basic structure specification IoC container, but to generate the Bean specific implementation is left subclass implementation. IoC container structure: AbstractBeanFactory maintaining a beanDefinitionMap hash table is used to define the class information storage ( BeanDefinition ). Acquisition Bean , if Bean already present in the container is returned, otherwise the call to doCreateBean a method for assembling a Bean . (Called present in the container, the container means can beanDefinitionMap obtain BeanDefinition further through its getBean() acquisition method Bean .) |
AutowireCapableBeanFactory |
Automatic assembly can be achieved BeanFactory . In this plant, the realization of a doCreateBean method of three steps: 1, by BeanDefinition saving the information in the instance of an object class; 2, the object is stored in BeanDefinition the order to prepare for the next acquisition; 3, its assembly properties. When assembling property, by BeanDefinition the maintenance of the PropertyValues collection classes, the String - Value key injected into Bean the properties to. If Value the type BeanReference is illustrated which is a reference (corresponding to the XML ref ), by getBean obtaining them, and then injected into the property. |
ApplicationContext
To ApplicationContext
interface the core emanating several classes, mainly on the front Resouce
, BeanFactory
, BeanDefinition
is encapsulated function, problem solving and IoC container obtained according to the address used.
The class name | Explanation |
---|---|
ApplicationContext |
Marker interface, inherited BeanFactory . Typically, a IoC container is to be achieved, it is necessary to pass ResourceLoader a acquisition Resource , including the configuration of the container, Bean the definition information. Next, BeanDefinitionReader the reading Resource of the BeanDefinition information. Finally, BeanDefinition kept in BeanFactory the container has been configured for use. Note that BeanFactory only implements Bean the assembly, acquisition, did not specify Bean the source of that it is BeanDefinition how loaded. The interface to BeanFactory and BeanDefinitionReader bonded together. |
AbstractApplicationContext |
ApplicationContext Abstract implementation, the interior contains a BeanFactory class. There are major methods getBean() and refresh() methods. getBean() Directly call a built- BeanFactory in getBean() method refresh() is used to achieve BeanFactory refresh, which tells BeanFactory of the use which resources ( Resource ) to load the class definition ( BeanDefinition ) information, which left sub-class implementation, to achieve loaded from different sources of different types of resources effect class definition. |
ClassPathXmlApplicationContext | 从类路径加载资源的具体实现类。内部通过 XmlBeanDefinitionReader 解析 UrlResourceLoader 读取到的 Resource ,获取 BeanDefinition 信息,然后将其保存到内置的 BeanFactory 中。 |
注 1:在 Spring 的实现中,对 ApplicatinoContext
的分层更为细致。AbstractApplicationContext
中为了实现 不同来源 的 不同类型 的资源加载类定义,把这两步分层实现。以“从类路径读取 XML 定义”为例,首先使用 AbstractXmlApplicationContext
来实现 不同类型 的资源解析,接着,通过 ClassPathXmlApplicationContext
来实现 不同来源 的资源解析。
注 2:在 tiny-spring 的实现中,先用 BeanDefinitionReader
读取 BeanDefiniton
后,保存在内置的 registry
(键值对为 String
- BeanDefinition
的哈希表,通过 getRigistry()
获取)中,然后由 ApplicationContext
把 BeanDefinitionReader
中 registry
的键值对一个个赋值给 BeanFactory
中保存的 beanDefinitionMap
。而在 Spring 的实现中,BeanDefinitionReader
直接操作 BeanDefinition
,它的 getRegistry()
获取的不是内置的 registry
,而是 BeanFactory
的实例。如何实现呢?以 DefaultListableBeanFactory
为例,它实现了一个 BeanDefinitonRigistry
接口,该接口把 BeanDefinition
的 注册 、获取 等方法都暴露了出来,这样,BeanDefinitionReader
可以直接通过这些方法把 BeanDefiniton
直接加载到 BeanFactory
中去。
设计模式
注:此处的设计模式分析不限于 tiny-spring,也包括 Spring 本身的内容
模板方法模式
该模式大量使用,例如在 BeanFactory
中,把 getBean()
交给子类实现,不同的子类 **BeanFactory
对其可以采取不同的实现。
代理模式
在 tiny-spring 中(Spring 中也有类似但不完全相同的实现方式),ApplicationContext
继承了 BeanFactory
接口,具备了 getBean()
功能,但是又内置了一个 BeanFactory
实例,getBean()
直接调用 BeanFactory
的 getBean()
。但是ApplicationContext
加强了 BeanFactory
,它把类定义的加载也包含进去了。
AOP 的实现
重新分析 IoC 容器
注:以下所说的 BeanFactory
和 ApplicationContext
不是指的那几个最基本的接口类(例如 BeanFactory
接口,它除了 getBean
空方法之外,什么都没有,无法用来分析。),而是指这一类对象总体的表现,比如 ClasspathXmlApplicationContext
、FileSystemXmlApplicationContext
都算是 ApplicationContext
。
BeanFactory 的构造与执行
BeanFactory
的核心方法是 getBean(String)
方法,用于从工厂中取出所需要的 Bean
。AbstractBeanFactory
规定了基本的构造和执行流程。
getBean
的流程:包括实例化和初始化,也就是生成 Bean
,再执行一些初始化操作。
doCreateBean
:实例化Bean
。
a.createInstance
:生成一个新的实例。
b.applyProperties
:注入属性,包括依赖注入的过程。在依赖注入的过程中,如果Bean
实现了BeanFactoryAware
接口,则将容器的引用传入到Bean
中去,这样,Bean
将获取对容器操作的权限,也就允许了 编写扩展 IoC 容器的功能的Bean
。initializeBean(bean)
: 初始化Bean
。
a. 从BeanPostProcessor
列表中,依次取出BeanPostProcessor
执行bean = postProcessBeforeInitialization(bean,beanName)
。(为什么调用BeanPostProceesor
中提供方法时,不是直接 post...(bean,beanName) 而是 bean = post...(bean,beanName) 呢?见分析1 。另外,BeanPostProcessor
列表的获取有问题,见分析2。)
b. 初始化方法(tiny-spring 未实现对初始化方法的支持)。
c. 从BeanPostProcessor
列表中, 依次取出BeanPostProcessor
执行其bean = postProcessAfterInitialization(bean,beanName)
。
ApplicationContext 的构造和执行
ApplicationContext
的核心方法是 refresh()
方法,用于从资源文件加载类定义、扩展容器的功能。
refresh
的流程:
loadBeanDefinitions(BeanFactory)
:加载类定义,并注入到内置的BeanFactory
中,这里的可扩展性在于,未对加载方法进行要求,也就是可以从不同来源的不同类型的资源进行加载。registerBeanPostProcessors(BeanFactory)
:获取所有的BeanPostProcessor
,并注册到BeanFactory
维护的BeanPostProcessor
列表去。onRefresh
:
a.preInstantiateSingletons
:以单例的方式,初始化所有Bean
。tiny-spring 只支持singleton
模式。
IoC 实现的一些思考与分析
分析 1:AOP 可以在何处被嵌入到 IoC 容器中去?
在 Bean
的初始化过程中,会调用 BeanPostProcessor
对其进行一些处理。在它的 postProcess...Initialization
方法中返回了一个 Bean
,这个返回的 Bean
可能已经不是原来传入的 Bean
了,这为实现 AOP 的代理提供了可能!以 JDK 提供的动态代理为例,假设方法要求传入的对象实现了 IObj
接口,实际传入的对象是 Obj
,那么在方法中,通过动态代理,可以 生成一个实现了 IObj
接口并把 Obj
作为内置对象的代理类 Proxy
返回,此时 Bean
已经被偷偷换成了它的代理类。
分析 2:BeanFactory 和 ApplicationContext 设计上的耦合
BeanFactory
中的 BeanPostProcessor
的列表是哪里生成的呢?是在 ApplicationContext
中的 refresh
方法的第二步,这里设计上应该有些问题,按理说 ApplicationContext
是基于 BeanFactory
的,BeanFactory
的属性的获取,怎么能依赖于 ApplicationContext
的调用呢?
分析 3:tiny-spring 总体流程的分析
总体来说,tiny-spring 的 ApplicaitonContext
使用流程是这样的:
1. ApplicationContext
完成了类定义的读取和加载,并注册到 BeanFactory
中去。
2. ApplicationContext
从 BeanFactory
中寻找 BeanPostProcessor
,注册到 BeanFactory
维护的 BeanPostProcessor
列表中去。
3. ApplicationContext
以单例的模式,通过主动调用 getBean
实例化、注入属性、然后初始化 BeanFactory
中所有的 Bean
。由于所有的 BeanPostProcessor
都已经在第 2 步中完成实例化了,因此接下来实例化的是普通 Bean
,因此普通 Bean
的初始化过程可以正常执行。
4. 调用 getBean
时,委托给 BeanFactory
,此时只是简单的返回每个 Bean
单例,因为所有的 Bean
实例在第三步都已经生成了。
JDK 对动态代理的支持
JDK 中几个关键的类:
类名 | 说明 |
---|---|
Proxy |
来自 JDK API。提供生成对象的动态代理的功能,通过Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法返回一个代理对象。 |
InvocationHandler |
来自 JDK API。通过 Object invoke(Object proxy, Method method,Object[] args) 方法实现代理对象中方法的调用和其他处理。 |
假设以下的情况:
- 对象
obj
实现了IObj
接口,接口中有一个方法func(Object[] args)
。 - 对象
handler
是InvocationHandler
的实例。
那么,通过 Proxy
的 newProxyInstance(obj.getClassLoader(), obj.getClass().getInterfaces(), handler
,可以返回 obj
的代理对象 proxy
。
当调用 proxy.func(args)
时,对象内部将委托给 handler.invoke(proxy, func, args)
函数实现。
因此,在 handler
的 invoke
中,可以完成对方法拦截的处理。可以先判断是不是要拦截的方法,如果是,进行拦截(比如先做一些操作,再调用原来的方法,对应了 Spring 中的前置通知);如果不是,则直接调用原来的方法。
AOP 的植入与实现细节
在 Bean
初始化过程中完成 AOP 的植入
解决 AOP 的植入问题,首先要解决 在 IoC 容器的何处植入 AOP 的问题,其次要解决 为哪些对象提供 AOP 的植入 的问题。
tiny-spring 中 AspectJAwareAdvisorAutoProxyCreator
类(以下简称 AutoProxyCreator
)是实现 AOP 植入的关键类,它实现了两个接口:
BeanPostProcessor
:在postProcessorAfterInitialization
方法中,使用动态代理的方式,返回一个对象的代理对象。解决了 在 IoC 容器的何处植入 AOP 的问题。BeanFactoryAware
:这个接口提供了对BeanFactory
的感知,这样,尽管它是容器中的一个Bean
,却可以获取容器的引用,进而获取容器中所有的切点对象,决定对哪些对象的哪些方法进行代理。解决了 为哪些对象提供 AOP 的植入 的问题。
AOP 中动态代理的实现步骤
动态代理的内容
首先,要知道动态代理的内容(拦截哪个对象、在哪个方法拦截、拦截具体内容),下面是几个关键的类:
类名 | 说明 |
---|---|
PointcutAdvisor |
切点通知器,用于提供 对哪个对象的哪个方法进行什么样的拦截 的具体内容。通过它可以获取一个切点对象 Pointcut 和一个通知器对象 Advisor 。 |
Pointcut |
切点对象可以获取一个 ClassFilter 对象和一个 MethodMatcher 对象。前者用于判断是否对某个对象进行拦截(用于 筛选要代理的目标对象),后者用于判断是否对某个方法进行拦截(用于 在代理对象中对不同的方法进行不同的操作)。 |
Advisor |
通知器对象可以获取一个通知对象 Advice 。就是用于实现 具体的方法拦截,需要使用者编写,也就对应了 Spring 中的前置通知、后置通知、环切通知等。 |
动态代理的步骤
接着要知道动态代理的步骤:
AutoProxyCreator
(实现了BeanPostProcessor
接口)在实例化所有的Bean
前,最先被实例化。- 其他普通
Bean
被实例化、初始化,在初始化的过程中,AutoProxyCreator
加载BeanFactory
中所有的PointcutAdvisor
(这也保证了PointcutAdvisor
的实例化顺序优于普通Bean
。),然后依次使用PointcutAdvisor
内置的ClassFilter
,判断当前对象是不是要拦截的类。 - 如果是,则生成一个
TargetSource
(要拦截的对象和其类型),并取出AutoProxyCreator
的MethodMatcher
(对哪些方法进行拦截)、Advice
(拦截的具体操作),再,交给AopProxy
去生成代理对象。 AopProxy
生成一个InvocationHandler
,在它的invoke
函数中,首先使用MethodMatcher
判断是不是要拦截的方法,如果是则交给Advice
来执行(Advice
由用户来编写,其中也要手动/自动调用原始对象的方法),如果不是,则直接交给TargetSource
的原始对象来执行。
设计模式
代理模式
通过动态代理实现,见分析1中的内容,不再赘述。
策略模式
生成代理对象时,可以使用 JDK 的动态代理和 Cglib 的动态代理,对于不同的需求可以委托给不同的类实现。
为 tiny-spring 添加拦截器链
tiny-spring 不支持拦截器链,可以模仿 Spring 中拦截器链的实现,实现对多拦截器的支持。
tiny-spring 中的 proceed()
方法是调用原始对象的方法 method.invoke(object,args)
。(参见 ReflectiveMethodInvocation
类)
为了支持多拦截器,做出以下修改:
- 将
proceed()
方法修改为调用代理对象的方法method.invoke(proxy,args)
。 - 在代理对象的
InvocationHandler
的invoke
函数中,查看拦截器列表,如果有拦截器,则调用第一个拦截器并返回,否则调用原始对象的方法。