从源码入手,一文带你读懂Spring AOP面向切面编程
之前《零基础带你看Spring源码——IOC控制反转》详细讲了Spring容器的初始化和加载的原理,后面《你真的完全了解Java动态代理吗?看这篇就够了》介绍了下JDK的动态代理。
基于这两者的实现上,这次来探索下Spring的AOP原理。虽然AOP是基于Spring容器和动态代理,但不了解这两者原理也丝毫不影响理解AOP的原理实现,因为大家起码都会用。
AOP,Aspect Oriented Programming,面向切面编程。在很多时候我们写一些功能的时候,不需要用到继承这么重的方法,例如对每个方法在执行前打log,在没有AOP的情况下,我们只能对每个方法都写一句打log的语句。如果是一个复杂点的功能,那么将会产生许多重复的代码,而且会对模块之间有更多的耦合。
然而,在AOP下,我们只需要通过特定的方法,就能直接切入代码,添加自定义的功能(后续再讲AOP里面的概念点)。
下面将从一个简单的示例入手,拆解示例的内容,通过源码分析,一步步带大家读懂AOP的原理实现。
使用示例
以下代码不以文字形式展示,若需要代码,可以到github查看完整Demo。
Demo:https://github.com/Zack-Ku/spring-aop-demo
Spring项目依然是用xml最原始的配置方式,为了只是能简单地阅读原理,否则会多很多自动配置的内容在里面。而AOP的配置用的是注解形式,因为毕竟看起来毕竟清晰,容易理解逻辑。
创建一个Gradle项目,添加对应的Spring与AOP的依赖。
(Gradle和Maven类似,都是自动化构建的工具。但与Maven相比,Gradle是基于groovy,采用DSL格式,具有更强的灵活性、简洁性、拓展性。现在连Spring的官方源码都是用Gradle的,可以说是一款面向未来的工具,后续也值得我们深入学习。)
创建一个Bean,TestBean。
创建AOP的Aspect。
然后写一个启动类,测试以上配置
运行结果:
com.zack.demo.TestBean.getStr()开始执行...
getStr():Testing!
com.zack.demo.TestBean.getStr()方法结束...
Demo:https://github.com/Zack-Ku/spring-aop-demo
示例解析与AOP术语概念
看到上面的结果,很容易猜想到,LogAspect作用了在TestBean上,使得每次执行TestBean上的方法时,都会执行对应的方法(before/after)。
LogAspect中带注解@Pointcut的allMethod(),是用来扫描程序中的连接点。当执行一个方法时,命中了连接点,则会根据不同的通知,执行对应的织入代码。在上面例子中,执行getStr()前会执行LogAspect中的before(),执行getStr()后会执行LogAspect中的after()。
具体的通知包含
- @Before,前置通知,执行方法前执行
- @AfterReturn,返回通知,正常返回方法后执行
- @After,后置通知,方法最终结束后执行,相当于finaly
- @Around,环绕通知,围绕整个方法
- @AfterThrowing,异常通知,抛出异常后执行
开发者在命中连接点时,可以通过以上不同的通知,执行对应方法。这就是AOP中的Advisor。
以上的内容其实已经把AOP核心的概念都已经点出来了,我们再深入具体的认识下其中的术语,
- Aspect,切面,一个关注点的模块。
例子中,LogAspect就是切面。 - JoinPoint, 连接点,程序执行中的某个点,某个位置。
例子中,testBean.getStr()是连接点。 - PointCut,切点,切面匹配连接点的点,一般与切点表达式相关,就是切面如何切点。
例子中,@PointCut注解就是切点表达式,匹配对应的连接点 - Advice,通知,指在切面的某个特定的连接点上执行的动作。
例子中,before()与after()方法中的代码。 - TargetObject,目标对象,指被切入的对象。
例子中,从ctx中取出的testBean则是目标对象。 - Weave,织入,将Advice作用在JoinPoint的过程。
以上概念看起来可以还比较难懂,可以通过以下一图(来源于网络)来理解
请各位读者和各位程序员,在阅读源码的时候,一定要先搞清楚基本概念,和一定一定要知道对应概念的英文,否则在看源码的时候,根本对不上号,使理解难度大大提高。因为源码都是英文写的。
至此AOP的基本使用和概念相信大家都有一定的了解,下面开始从源码入手,去探索整个Spring AOP的实现。
源码分析
上面的例子之所以能完成AOP的代理,只因为Spring的xml配置里面加了这一句
< aop : aspectj-autoproxy / >
加上了这一个配置,使得整个Spring项目拥有了AOP的功能。全局搜索下aspectj-autoproxy这个字段,可以发现,是这个类AspectJAutoProxyBeanDefinitionParser解析了这个元素。
其中的parse方法调用的是AopNamespaceUtils类中的registerAspectJAnnotationAutoProxyCreatorIfNecessary。这个方法作用是初始化一个AOP专用的Bean,并且注册到Spring容器中。
解析这三个操作,
- 第一句,注册一个AnnotationAwareAspectJAutoProxyCreator(称它为自动代理器),这个Creator是AOP的操作核心,也是扫描Bean,代理Bean的操作所在。
- 第二句,解析配置元素,决定代理的模式。其中有JDK动态代理,还有CGLIB代理,这部分后续会再细讲。
- 第三句,作为系统组件,把Creator这个Bean,放到Spring容器中。让Spring实例化,启动这个Creator。
自动代理器
下面我们来细看AnnotationAwareAspectJAutoProxyCreator是怎么对Bean做AOP的。
AnnotationAwareAspectJAutoProxyCreator的父类AbstractAutoProxyCreator,里面实现了BeanPostProceesor接口的postProcessAfterInitialization方法(该方法在一个Bean加载到Spring后会执行)。
关联注释描述可知,当一个bean加载完后,执行了该方法,会生成一个新的代理对象,返回context中加载。
下面重点看其中的wrapIfNecessary方法。讲述了整个AOP的核心流程,是Spring AOP最最最核心的代码所在。
看到红框的两个核心方法,可以知道,先从刚加载的Bean中扫描出所有的advice和advisor,然后用它来创建一个代理对象。
获取Advisor
先看如何扫描出advice和advisor。
一步步Debug getAdvicesAndAdvisorsForBean(),找到BeanFactoryAspectJAdvisorsBuilder中的buildAspectJAdvisors方法。
该方法就是找出Spring容器中存在的AspectBean,然后返回所有AspectBean中的Advisor。
示例中,LogAspect就是AspectBean,然后LogAspect中的before和after方法就是Advisor。
所以最终返回了LogAspect中的Advisor(before和after)。
创建代理
拿到了所有的Advisor后,就进入了创建代理的流程了createProxy()。
这些入参,对比上一篇讲过的动态代理,其实非常相似。
- beanClass,加载到Spring,触发AOP的bean类
- targetSource,目标对象,示例中则是从ctx中取出的testBean
- specificInterceptors,指定Advisor,示例中则是before和after的方法。
下面来具体看下代理的过程
代码可以概括为,创建一个proxyFactory对象,然后把上面的参数都丢到这个这个工厂里,最后从proxyFactory获取一个代理对象。
来看看ProxyFactory的getProxy方法是怎么生成代理对象的。
Debug该方法,可以在DefaultAopProxyFactory中createAopProxy看到
工厂会根据配置与目标对象的类型,选择用JDK动态代理(参考《你真的完全了解Java动态代理吗?看这篇就够了》)还是CGLIB的代理(CGLIB具体在后续讲)。
代理后的对象放回ctx中,然后当程序执行的时候,会直接调用这个代理类。
至此整个AOP的代理流程就结束了。下面来了解下CGLIG代理与JDK代理的不同
CGLIB与JDK代理区别
CGLIB(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。Hibernate支持它来实现PO(Persistent Object 持久化对象)字节码的动态生成。
回顾下JDK代理,JDK代理需要一组需要实现的接口,然后通过这些接口获取构造方法,用这个构造方法和InvocationHandler,实例化一个对象出来。所以JDK的方式是基于接口的。
而CGLIB的代理是基于类的,用目标类生成一个子类,子类重写父类的方法,从而达到动态代理的效果。CGLIB的使用和实现等后面有机会再详细介绍。目前暂时只要理解两者不同的使用场景就足够了。
总结
回顾下Spring AOP的流程
- Spring加载自动代理器AnnotationAwareAspectJAutoProxyCreator,当作一个系统组件。
- 当一个bean加载到Spring中时,会触发自动代理器中的bean后置处理
- bean后置处理,会先扫描bean中所有的Advisor
- 然后用这些Adviosr和其他参数构建ProxyFactory
- ProxyFactory会根据配置和目标对象的类型寻找代理的方式(JDK动态代理或CGLIG代理)
- 然后代理出来的对象放回context中,完成Spring AOP代理
相信大家通过阅读本文,对Spring的AOP处理有一定的认识。想更深入地了解,探索每一步,每一行代码的实现,可以下载Demo源码,一步步地调试
Demo:https://github.com/Zack-Ku/spring-aop-demo
更多技术文章、精彩干货,请关注
博客:zackku.com
微信公众号:Zack说码
从源码入手,一文带你读懂Spring AOP面向切面编程的更多相关文章
- 一文带你读懂什么是vxlan网络
一个执着于技术的公众号 一.背景 随着云计算.虚拟化相关技术的发展,传统网络无法满足大规模.灵活性要求高的云数据中心的要求,于是便有了overlay网络的概念.overlay网络中被广泛应用的就是vx ...
- 一文带你读懂zookeeper在大数据生态的应用
一个执着于技术的公众号 一.简述 在一群动物掌管的世界中,动物没有人类聪明的思想,为了保持动物世界的生态平衡,这时,动物管理员-zookeeper诞生了. 打开Apache zookeeper的官网, ...
- 实战 | 一文带你读懂Nginx反向代理
一个执着于技术的公众号 前言 在前面的章节中,我们已经学习了nginx基础知识: 给小白的 Nginx 10分钟入门指南 Nginx编译安装及常用命令 完全卸载nginx的详细步骤 Nginx 配置文 ...
- React16源码解读:开篇带你搞懂几个面试考点
引言 如今,主流的前端框架React,Vue和Angular在前端领域已成三足鼎立之势,基于前端技术栈的发展现状,大大小小的公司或多或少也会使用其中某一项或者多项技术栈,那么掌握并熟练使用其中至少一种 ...
- 从源码层面聊聊面试问烂了的 Spring AOP与SpringMVC
Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以.但今天笔者带大家一起深入浅出源码,看看他的原理.以期让印象更加深刻,面试的时候游刃有余. Sp ...
- 曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 【并发编程】一文带你读懂深入理解Java内存模型(面试必备)
并发编程这一块内容,是高级资深工程师必备知识点,25K起如果不懂并发编程,那基本到顶.但是并发编程内容庞杂,如何系统学习?本专题将会系统讲解并发编程的所有知识点,包括但不限于: 线程通信机制,深入JM ...
- 一文带你读懂 Mysql 和 InnoDB存储引擎
作为一名开发人员,在日常的工作中会难以避免地接触到数据库,无论是基于文件的 sqlite 还是工程上使用非常广泛的 MySQL.PostgreSQL,但是一直以来也没有对数据库有一个非常清晰并且成体系 ...
- 一文带你读懂什么是docker
一 简介 1.了解Docker的前生LXC LXC为Linux Container的简写.一种轻量级的内核虚拟化技术,隔离进程和资源. Linux Container有点像chroot,提供了一个拥有 ...
随机推荐
- 往Layout中动态添加View
需要注意几个方法:基本上所有的方法参数单位是px 1.设置View的宽高: LinearLayout.LayoutParams params = new LinearLayout().LayoutPa ...
- 启动Eclipse时,弹出failed to load the jni shared library
JDK版本和Eclipse版本不同的问题,JDK版本为64位,Eclipse版本为32位.
- 随机森林(Random Forest)详解(转)
来源: Poll的笔记 cnblogs.com/maybe2030/p/4585705.html 1 什么是随机森林? 作为新兴起的.高度灵活的一种机器学习算法,随机森林(Random Fores ...
- 如何得知 kernel 或 android 已開機多久時間
adb shell cat /proc/uptime 中的第一個數字, adb shell cat "/proc/uptime" 210.79 312.76 或者是 kernel ...
- EF添加ADO.NET实体模型处直接选择Oracle数据源
上一文介绍了如何下载Mysql for vs Tools来进行Mysql的ADO.NET实体模型数据源选择,今天将Oracle的测试了下.步骤如下: 1.在你项目Model层中nuget安装选中项 2 ...
- 《Java编程思想》阅读笔记二
Java编程思想 这是一个通过对<Java编程思想>(Think in java)进行阅读同时对java内容查漏补缺的系列.一些基础的知识不会被罗列出来,这里只会列出一些程序员经常会忽略或 ...
- python_day3学习笔记
set集合 python的set是一个无序不重复元素集,基本功能包括关系测试和消除重复元素. 集合对象还支持并.交.差.对称差等. sets 支持 x in set. len(set).和 for x ...
- LeetCode解题报告—— Swap Nodes in Pairs & Divide Two Integers & Next Permutation
1. Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. For e ...
- Google的C++开源代码项
转:http://blog.csdn.net/wenrenhua08/article/details/40040903 v8 - V8 JavaScript EngineV8 是 Google 的 ...
- ubuntu 软件包(package)更换源(source)为阿里云镜像 update&upgrade
在ubuntu下用apt-get install安装软件时,发现package list中没有所需的软件, 估计可能是package list太旧了,于是需要apt-get update & ...