关于Spring AOP的灵魂十问

点击↑上方↑蓝色“编了个程”关注我~

荒腔走板

今天没有这个环节。。。

什么是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 AOP的灵魂十问

推荐阅读

  • 关于Spring IoC的那些事

  • 源码解析 – Spring如何实现IoC的r>

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览92930 人正在系统学习中

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年8月5日
下一篇 2020年8月5日

相关推荐