Spring IOC和Spring AOP的实现原理(源码主线流程)
写在前面
正本文参考了《spring技术内幕》和spring 4.0.5源码。本文只描述原理流程的主线部分,其他比如验证,缓存什么可以具体参考源码理解。
Spring IOC
一、容器初始化
容器的初始化首先是在对应的构造器中进行,在applicationContext的实现类构造器中,首先对参数路径中的${}进行了处理,用系统变量替换(setConfigLocations方法)然后调用refresh方法(这个就是最核心的容器初始化方法)。
1、Resource定位:
在refresh方法中调用obtainFreshBeanFactory方法告诉子类刷新beanfactory(其中是调用refreshBeanFactory刷新后getBeanFactory获取刷新后的factory返回)。在刷新过程refreshBeanFactory中如果factory已经有了要消除再新建factory,其中loadBeanDefinitions是加载bean定义的方法。
在loadBeanDefinitions方法中创建了BeanDefinitionReader的实现类调用其loadBeanDefinitions方法(这个方法是重载方法,参数有为Resource的也有为String路径的,getConfigResources方法(默认返回null,子类重写,如ClassPathXmlApplicationContext类)和getConfigLocations方法获得Resource集合和资源路径集合(一般一个为空,一般是将容器的参数path设定为configLocations,ClassPathXmlApplicationContext有一种构造器是不设定configLocations而是直接用参数path生成ClassPathResource集合设定为configResources)分别进行load,实际上以路径为参数的重载方法在定位完Resource也会调用以resource为参数的loadBeanDefinitions来解析载入BeanDefinition,这个是第二步在下面介绍)。
在BeanDefinitionReader的loadBeanDefinitions(path参数)方法中根据ResourceLoader类型以两种方式加载(如果是ant正则表达式方式的(如PathMatchingResourcePatternResolver)一个路径定位多个resource或者默认方式(applicationContext继承的是DefaultResourceLoader实现方式)定位一个resource),分别调用ResourceLoader的getResource(以/开头的构建ClassPathContextResource,以classpath开头的去掉classpath构建ClassPathResource,如果都不是的尝试构建UrlResource,如果构建失败就调用getResourceByPath这个具体applicationContext实现类里重写的方法构建特定Resource,如FileSystemXmlApplicationContext就是FileSystemResource)或getResources(PathMatchingResourcePatternResolver的正则方式这里不详细描述)完成Resource定位。
2、从Resource中解析和载入BeanDefinition:
同样在BeanDefinitionReader的loadBeanDefinitions中调用完resourceLoader的getResource获取Resource后将resource作为参数调用自己(BeanDefinitionReader)的loadBeanDefinitions(是一个接口方法给子类实现,因为不同的reader加载resource的方式不同)载入BeanDefinition。
例如XmlBeanDefinitionReader是对XML文件的IO操作,(将现在要处理的Resource加入当前线程正在处理(ThreadLocal)的Resource集合中)首先从resource中拿出InputStream封装成InputSource调用自身的doLoadBeanDefinitions方法。
doLoadBeanDefinitions方法中调用doLoadDocument方法封装成Document-----是用validationMode(默认是自动校验方式,意思是如果没有显示定义校验的方式就用XSD方式)和DocumentLoader(XmlBeanDefinitionReader中默认的是DefaultDocumentLoader)等参数调用DocumentLoader的loadDocument方法将Resource封装成Document类(具体封装方式不做介绍,有兴趣的可以自己了解一下,用的是builder模式做的)调用registerBeanDefinitions方法解析载入bean。
registerBeanDefinitions方法是用BeanDefinitionDocumentReader的registerBeanDefinitions具体解析Document(树形结构,从root(就是beans标签)开始往下解析)中每个element各个标签的解析和载入。其中如果是bean标签BeabDefinitionParserDelegate的parseBeanDefinitionElement方法对XML元素的信息按照spring的bean的规则进行解析(property的解析,当中value和ref解析方式不同,如果是value构建TypedStringValue, 如果ref的话构建RuntimeBeanReference,这个在之后依赖注入的时候用到,还有id,name,等属性的解析)得到的BeanDefinition的封装BeanDefinitionHolder(包括BeanDefinition,beanName(这里是标识符的意思,如果有id,id做标识符,没有id,name属性中第一个别名做标识符)和别名列表(name属性中的内容,如果没有id,name中第一个不作为别名而是标识符))来进行下一步bean的注册(BeanDefinitionReaderUtils.registerBeanDefinition)。
其他如import,alias等标签自行看源码理解。
3、BeanDefinition在IOC容器的注册
BeanDefinitionReaderUtils.registerBeanDefinition用BeanDefinitionRegistry(DefaultListableBeanFactory)的registerBeanDefinition方法注册beanName和BeanDefinition(就是把beanName加入到一个已经注册的bean的beanName的Set中,然后put到beanName对应BeanDefinition的map中,其中如果不允许覆盖并且有同名beanName要报错)。再用BeanDefinitionRegistry的registerAlias方法注册beanName和别名列表(put到一个beanName对应alias的map中,其中如果有alias跟beanName相同的要移除)。
二、IOC容器依赖注入

1、getBean第一次调用lazy-init的bean
是以BeanFactory的getBean方法为入口触发的(实现在AbstractBeanFactory实现类中)。如果是单例会缓存起来只加载一次,如果是FactoryBean这种特殊的bean会把这个bean的实例传入getObjectForBeanInstance方法获得FactoryBean产生的bean(调用FactoryBean的getObject方法,这就是自定义的FactoryBean要重写的方法,AOP也是这个原理,详情见下方AOP分析)。在第一次载入时要先判断这个BeanDefinition在当前BeanFactory有没有,没有就从双亲BeanFactory中找,一直递归。
找到后要验证是否存在递归依赖,有则报错无则设置当前bean依赖bean的依赖关系到两个map中(一个是被依赖map,一个是依赖map),其中:
(1)如果是单例第一次载入就调用getSingleton方法(方法中回调了参数中ObjectFactory的getObject方法,这里重写了这个方法调用createBean)获得实例用getObjectForBeanInstance获得FactoryBean产生的bean(如果它是FactoryBean的话)。
(2)如果是prototype加载调用createBean后调用getObjectForBeanInstance。
(3)如果是其他scope类型:request、session和global session,这三种就用scope.get获取实例(和getSingleton类似回调重写的getObject也就是调用createBean)后调用getObjectForBeanInstance。
最后如果getBean指定了requiredType要检验获取的bean能不能转化成指定的类型不能的话就报错。
createBean方法就是生成bean的方法并对一些比如init-method属性、后置处理器等一些初始化进行了处理。方法中在实例化之前判断是否有post-processor,如果有这样的processor则短路指定bean的创建,直接返回一个proxy而不是指定的bean(这种processor可以指定生成一个其他类型的对象)没有的话用doCreateBean创建bean返回。
doCreateBean是用createBeanInstance生成BeanWrapper(包装bean)之后用populateBean向其中的bean完成依赖bean的注入(autowire等)。
createBeanInstance创建beanWrapper时分三类进行处理:
(1)如果有工厂方法,调用instantiateUsingFactoryMethod创建。
(2)如果是构造器注入的方式调用autowireConstructor。
(3)简单方式调用instantiateBean。调用的是策略类(默认SimpleInstantiationStrategy)的instantiate而其中又是通过bean方法是否有跟IOC容器同名的(会被覆盖)来分两类处理(没同名方法的从BeanDefinition中拿出class直接用jdk的反射拿构造器来newinstance一个实例,如果有同名的则是用CGLIB的方式来new一个实例)。
populateBean为生成的bean依赖注入,先对非简单类型属性有autowire的进行处理,判断这个属性在之前解析载入beanDefinition时property里有没有,有的话进行getBean初始化后放入PropertyValue集合中(这个就是propertyname和value的封装),然后更新依赖map,再对非autowire的或一般属性进行注入,有要转化的要经过valueResolver的转化(如果是RuntimeBeanReference之前载入时XML中配置是ref的就getBean(如果在双亲BeanFactory中就从双亲中取)获得后也放到PropertyValue集合中,也要更新依赖map)。最后再注入到bean中,这里说的注入其实真实发生在最后的BeanWraper的setPropertyValue(propertyValue集合)方法,具体实现就是通过反射的方式获得setter方法赋值。
2、lazy-init==false初始化(只对singleton,也是默认方式)
在refresh方法中的finishBeanFactoryInitialization方法中进行初始化(实际也是调用getBean方法)。
Spring AOP
ProxyFacotryBean是FacotryBean的一种实现,FacotryBean要产生bean都要重写getObject方法,而ProxyFacotryBean这里的这个getObject正是为代理做了准备并返回代理对象。首先用initializeAdvisorChain(第一次去取代理对象时初始化一遍)初始化Advisor链后对于singleton和prototype进行区分生成对应的proxy。
1、初始化Advisor链
initializeAdvisorChain初始化Advisor链是遍历ProxyFacotryBean中配置的interceptorNames,如果结尾有通配符只能是ListableBeanFacotory来加载否则报错,去掉结尾通配符*后调用addGlobalAdvosor(这个是获取ListableBeanFacotory的所有globalAdvisorNames和globalInterceptorNames,分别遍历用getBean(beanName)获取advice,把其中符合通配符格式的advice调用addAdvisorOnChainCreation封装成advicsor后添加到Advisor链,如果结尾没有通配符的情况下无论是singleton还是prototype在获得advice后都要用addAdvisorOnChainCreation方法注册到advisor链上。
addAdvisorOnChainCreation用namedBeanToAdvisor方法把advice包装成advisor,判断如果advice是单例singleton的话是用AdvisorAdapterRegistry(默认DefaultAdvisorAdapterRegistry单例)wrap方法判断如果这个advice是MethodInterceptor或者AdvisorAdapterRegistry三种固定的adapter(before,afterreturning,throws)如果任一adapter支持的话(支持不支持就是在具体的adapter中判断advice是不是这个adapter对应具体的advice类的子类)就封装成DefaultPointcutAdvisor返回。如果是prototype的话不获取getBean,而是直接用name包装成PrototypePlaceholderAdvisor。
2、生成代理类
以singleton为例,singleton代理的生成getSingletonInstance方法。是用AopProxyFactory(在构造器中设定了默认的DefaultAopProxyFactory)的createAopProxy方法根据ProxyFacotryBean中配置的target判断是否是个接口(实际上不是这么简单的区分,具体看源码了解)来创建不同AopProxy的子类(JdkDynamicAopProxy或者ObjenesisCglibAopProxy(CglibAopProxy的子类,增加了ObjenesisStd))调用他们各自的getProxy方法以不同的方式创建代理对象返回。
JdkDynamicAopProxy就是以动态代理的方式构建代理对象返回(具体动态代理原理自行了解哦)。
CglibAopProxy就是以Cglib的方式进行代理,Cglib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。具体细节超出这文章的范围拉。
prototype代理的方式大致相同有些许的差别也不做介绍,可以参考源码。
3、调用时拦截
在调用目标类的方法时因为代理调用的是invoke(jdk动态代理)或者intercept(cglib)。在invoke(jdk动态代理)或者intercept(cglib)中根据目标类被调用方法分别处理。
如果是hashCode和equals方法直接调用代理类中重写了的hashCode和equals方法(具体参考源码)。
如果是Adviced接口中定义的方法(ProxyFactoryBean就是Adviced接口实现类)直接以反射的方式拿到method调用方法(AopUtils的invokeJoinpointUsingReflection方法)。
其他情况就是拿到拦截器链(只初始化一次,每次调用时有个currentInterceptorIndex记录处理到第几个拦截器)调用拦截器的proceed方法前进调用。
proceed前进调用不是递归,其中用matcher进行匹配,如果匹配上调用拦截器的invoke方法,匹配不上就直接继续前进调用,拦截器interceptor的invoke方法就是通知方法(自己实现的如afterReturning等)对目标方法(实际是拦截器链的proceed前进调用)的具体加强,就是顺序问题等等。
直到拦截器链前进到底调用target目标类的对应方法(jdk反射获取method调用)。
初始化拦截器链是通过遍历之前IOC容器getBean获取到advisor链中的Advisor,通过AdvisorAdapterRegistry当中设置的3种adapter(before,afterreturning,throws)的supportsAdvice判断是否支持该advisor,如果支持就将advisor中的advice注册成不同的AdviceInterceptor列表(一个advisor可以被多个adapter支持,因为只要自己写的通知类实现多种advice接口即可)都加入到拦截器链。
Spring IOC和Spring AOP的实现原理(源码主线流程)的更多相关文章
- Spring AOP的实现及源码解析
在介绍AOP之前,想必很多人都听说AOP是基于动态代理和反射来实现的,那么在看AOP之前,你需要弄懂什么是动态代理和反射及它们又是如何实现的. 想了解JDK的动态代理及反射的实现和源码分析,请参见下面 ...
- Spring Cloud系列(四):Eureka源码解析之客户端
一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...
- Spring PropertyResolver 占位符解析(二)源码分析
Spring PropertyResolver 占位符解析(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) ...
- Spring Boot启动命令参数详解及源码分析
使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...
- SharedPreferences 原理 源码 进程间通信 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Laya Timer原理 & 源码解析
Laya Timer原理 & 源码解析 @author ixenos 2019-03-18 16:26:38 一.原理 1.将所有Handler注册到池中 1.普通Handler在handle ...
- 深入理解Faiss 原理&源码 (一) 编译
目录 深入理解Faiss 原理&源码 (一) 编译 mac下安装 安装mac xcode工具包 安装 openblas 安装swig 安装libomp 编译faiss 附录 深入理解Faiss ...
- Spring IOC/DI和AOP原理
一 IOC/DI 1. 概念机原理 IOC: Inversion of Control(控制反转)是一种设计思想,就是容器控制应用程序所需要外部资源的创建和管理,然后将其反转给应用程序.对象及其依赖对 ...
- spring IOC DI AOP MVC 事务, mybatis 源码解读
demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...
随机推荐
- Oracle 在函数或存储过程中执行sql查询字符串并将结果值赋值给变量
请看黄色部分 --区县指标 THEN TVALUE_SQL := 'SELECT TO_CHAR(' || CUR_ROW.MAIN_FIELD || ') FROM ' || CUR_ROW.END ...
- C++模板的特化
C++类模板的三种特化,讲得比较全面 By SmartPtr(http://www.cppblog.com/SmartPtr/) 针对一个模板参数的类模板特化的几种类型, 一是特化为绝对类型(全特化) ...
- 利用Costura.Fody制作绿色单文件程序(C#程序(含多个Dll)合并成一个Exe)
原文:利用Costura.Fody制作绿色单文件程序(C#程序(含多个Dll)合并成一个Exe) 开发程序的时候经常会引用一些第三方的DLL,然后编译生成的exe文件就不能脱离这些DLL独立运行了.这 ...
- Zookeeper-集群与单机实践
我用的是linux,CentOS7.3,zookeeper的版本是3.4.6,工具XShell.上传zookeeper的压缩包后我们开始操作. 集群模式: 1.解压zookeeper,路径随意 tar ...
- RabbmitMQ-组成及简单使用
什么是MQ? MQ全程Message Queue,消息队列(MQ)是一种应用程序对应用程序的通信方法.MQ是消费者-生产者模型的典型代表.一端往消息队列中不断写消息而另一端则可以读取队列中的消息. R ...
- 大话设计模式之模板模式 C#
学无止境,精益求精 十年河东,十年河西,莫欺少年穷 今天一起探讨模板模式,如下: 一.概念 上一篇文章讲了大话设计模式:原型模式,原型模式主要是通过Clone()方法<深浅复制>,创建新的 ...
- 线程中join()的用法
Thread中,join()方法的作用是调用线程等待该线程完成后,才能继续用下运行. public static void main(String[] args) throws Interrupted ...
- GeForce Experience关闭自动更新
GeForce Experience驱动更新很烦,而且有时更新后就打不开了,找到种方法关闭更新 1.安装并登陆 2.打开 C:\ProgramData\NVIDIA Corporation 3.进入D ...
- Ceph常规操作及常见问题梳理
Ceph集群管理 每次用命令启动.重启.停止Ceph守护进程(或整个集群)时,必须指定至少一个选项和一个命令,还可能要指定守护进程类型或具体例程. **命令格式如 {commandline} [opt ...
- C. Make It Equal
链接 [http://codeforces.com/contest/1065/problem/C] 题意 给你n个高度hi的塔,让你把高的部分切掉,使得最后所有塔一样高,而且每次切的高度之和不大于k ...