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

这个方法内部先取出所有的通知,然后给它们都加上一个AspectJPrecedenceComparator。这个Comparator会取出通知所在的Bean的@Order注解定义的优先级,按照这个优先级来排序。其实我们有时候使用其它Bean也会用到这个注解。
所以如果你会对一个方法声明多个通知,那可以使用@Order注解来定义这多个通知的优先级。@Order定义的值越小,其内部定义的通知对应的拦截器就会在调用链的越外层。
注意,如果是同一个切面类里面定义的多个通知,会按照方法声明的先后顺序来排序。
AOP和循环依赖的那些事h1>
大家可能会遇到过或者听说过Spring的循环依赖的问题。Spring使用了“三级缓存”来解决Bean的循环依赖,但可能很多人不知道为什么要使用三级缓存,其实这个也跟AOP有关。
如果没有AOP,其实Spring使用二级缓存就可以解决循环依赖的问题。若使用二级缓存,在AOP情形下,注入到其他Bean的,不是最终的代理对象,而是原始目标对象。
因为Spring对Bean有一个生命周期的定义,而代理对象是在Bean初始化完成后,执行后置处理器的时候生成的。所以不能在二级缓存的时候就直接生成代理对象,放进缓存。
使用AOP有什么弊端h1>
AOP不是万能的,使用AOP也是有一些弊端的。个人觉得对大的弊端就是让代码可读性变差了,因为它并不是一个显式的调用,所以很有可能未来接手代码的人并不清楚这个方法被AOP代理了。
笔者之前遇到过一个项目,就是使用了AOP来做权限控制,但这个权限控制不是那种简单的Access,而是要去查数据库里面的一些字段,比如状态之类的,还有复杂的逻辑判断。这种情况下,如果使用AOP来做,代码的可读性就不强,出了问题比较难以排查。
我是Yasin,一个有颜有料又有趣的程序员。
个人 站:https://yasinshaw.com
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览92925 人正在系统学习中 相关资源:口腔管理软件牙医管家口腔管理软件v3.11.0.16标准版_牙医管家…
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!