点击↑上方↑蓝色“编了个程”关注我~
荒腔走板
今天没有这个环节。。。
什么是AOPh3>
AOP全称是Aspect Oriented Programming,翻译过来是“面向切面”编程。在Java语言里,一切皆对象,所以我们通常说Java语言是一门“面向对象”编程的语言。而面向切面编程,不是要取代面向对象编程,而是对它的一种补充。
AOP要解决的问题是用一个“横切面”的方式,来统一处理很多对象都需要的,相同或相似的功能,减少程序里面的重复代码,让代码变得更干净,更专注于业务。
AOP有一些专业的概念和术语。切面、连接点、通知、切点、引入、目标对象、代理对象、织入等。很多都是直接根据英文单词翻译过来的,这里我们就不详细介绍这些概念了,Spring官方文档AOP那一章节开头就有详细的介绍。但是这篇文章会从使用的视角来顺带介绍其中的一些术语。
打开AspectJ支持
使用这个注解可以打开AspectJ支持,以后就可以愉快地在Spring中使用AspectJ语法了。需要注意的是,「如果你使用的是SpringBoot,这个注解已经默认加上了」,不需要再手动写在你的代码里。
声明一个切面
使用注解可以声明一个切面。其实就是一个类,在这个类里面去定义切点、通知等东西。
声明一个切点
所谓“切点”,就是你要去切什么地方。Spring只支持去切「被Spring管理的Bean的方法」。声明一个切点也很简单,在我们上面声明的切面类里面,用下面这种形式创建一个方法就行了:
使用注解,里面是切点的表达式。需要注意这个方法的返回值必须是的。关于表达式,有一些Spring支持的关键字,这里就不一一细讲了,官方文档上Supported Pointcut Designators这一节有详细介绍。我们最常用的应该是和这两个了。
其中也有一些通配符,包括, , , , , 等等,都是有不同的含义和作用。具体可以在官方文档这一节了解。
声明通知
“通知”,指的是在什么时候去执行我们定义的AOP逻辑。Spring提供了这样几种通知:
-
@Before
-
@AfterReturning
-
@AfterThrowing
-
@After,其实质是AfterFinally
-
@Around
大家看名字应该都知道是啥意思了吧。其中,包含上面所有的功能,使用起来更强大、灵活。
一个完整的AOP定义大概长这样:
需要注意的是「这里的注解是必须要加的,不然Spring不会自动扫描这个类」,那你定义的切面、切点和通知也就无效了。
一个切面里面可以有多个切点和多个通知,并且一个切点也可以被多个通知使用。
Spring用什么实现的AOPh3>
前面我们提到过AspectJ,AspectJ使用的是编译期和类加载时进行织入,而Spring AOP利用的是「运行时织入」。而如果使用运行时织入,就要用到“动态代理”的技术。
先来聊聊动态代理。AOP其实是设计模式中的“代理模式”的一种应用,那什么是代理模式呢举个很常见的例子,就是游戏代练了。
游戏代练高手收了土豪的钱,登上土豪的账 ,一路连胜打上最强王者,然后完成交易,功成身退。而游戏中的队友和对手,甚至游戏官方并不知道这是一个代练,还以为是土豪本人,私下里纷纷夸赞土豪技术了得,满足了土豪的虚荣心。。。
代理模式说简单点,就是通过「把原本的对象进行包装,提供一些额外的能力」,但是「对外部而言是无感知的」,并不知道或者并不在意这个对象是不是被代理了。
静态代理和动态代理的区别在于,静态代理把要代理的类型已经写死了,一个代理只能代理一种类型。而动态代理就不一样了,一个代理可以代理多种类型。还是拿游戏代练举例,静态代理可能就是这个代练只能代练LOL这一种游戏。而动态代理,这个代练可以代练所有游戏,是不是一听就是大高玩。
Spring底层使用了两种方式来实现动态代理,一种是Java自带的动态代理,另一种是CGLib。如果是使用JDK动态代理生成的代理对象,Debug可以看到,而如果是CGLib生成的对象,可以看到是。
那Spring具体是使用的哪种方式呢有很多文章说,Spring默认产生代理对象的行为是:如果你的Bean有对应的接口,是使用的基于JDK的动态代理,否则是使用CGLIB。但这样说其实不准确,Spring用了下面这个配置来控制它,如果这个配置是false,才是上面我们说的这个逻辑。而如果这个配置是true,则所有的要使用AOP的Bean都使用CGLIB代理,不管它是不是有接口。而我们使用最新版的SpringBoot的话,这个值默认就是true。
所以现在「如果使用SpringBoot的话,我们的AOP代理对象都是用CGLIB生成的」。
JDK和CGLib动态代理有什么区别h3>
两者实现的原理不同,JDK动态代理是基于Java反射来实现的,而CGLIB动态代理是基于修改字节码,生成子类来实现的,底层是使用的asm开源库。
两者都有一些限制,JDK动态代理,Bean必须要有接口;CGLIB不能对final类或方法做代理。
哪些方法可以被代理h3>
如果是使用JDK动态代理,那只有public方法可以被代理。而如果使用CGLIB,除了private方法,都可以被代理。(当然,final方法除外)。
另一个比较有意思的问题是,「如果两个方法在同一个类里面,一个方法调用另一个方法是不会走代理的」。只有一个Bean调用另一个Bean的方法,才会走代理。
上面两个特性也就解释了为什么有时候你的不生效的原因:
-
在私有方法上不生效
-
在final方法上不生效
-
同一个类里面方法互相调用不生效
代理对象是什么时候生成的h3>
在上一篇文章中,我们了解了Spring的Bean是如何生成的。在Spring启动的时候,会去调用getBean方法,完成Bean的初始化工作。而在getBan里面,初始化Bean后,会去调用Bean的。这个代码可以通过getBean方法Debug找进去。这里就不细讲Debug过程了,放一张调用栈的截图吧。
同一个方法被多次代理怎么办h3>
一个方法是有可能被多次代理的。Spring AOP不仅仅是基于代理模式,还使用了“拦截器”模式。这个拦截器,有点像Web的拦截器一样,在目标对象上包了一层又一层,形成一个拦截器链。那它们的顺序是如何决定的呢p>
我们在前面的源码解析中,有一个分支,逻辑是去除当前这个代理对象的所有“通知”,然后排序。代码在AspectJAwareAdvisorAutoProxyCreator类的方法里。调用栈:
我是Yasin,一个有颜有料又有趣的程序员
个人 站:https://yasinshaw.com
这里很多技术干货,关注肯定不后悔
听说,转发和在看的人都升职加薪了

推荐阅读
-
关于Spring IoC的那些事
-
源码解析 – Spring如何实现IoC的r>
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览92930 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!