JAVA AOP理论与实践

之前的博客写过关于JAVA反射、注解的相关理论和实践,另外一个比较相关又晦涩的东西就是AOP切面了。

目前我对JAVA的学习思路,主要是优先掌握这些最晦涩的部分,因为语言的其他部分无非就是基础语法和类库,基于此前我个人的开发经验来说都不是什么问题。

这个学习的动力,主要来源于对Java web框架的各种高大上设计的好奇心,这在其他语言WEB框架设计里虽然能找到一些模仿的影子,但毕竟还是不够透彻。

废话不多说,项目地址:https://github.com/owenliang/java-aop

切面理论

用途

就是给原有的方法外面加一层包装,如果用过python的装饰器语法就知道了,完全就是一码事,换了个叫法。

好处当然就是不用改原有的方法代码,对于JAVA来说可以做到原有代码一行不改,文件一行不变。

切面Aspect

JAVA在实现这个能力的时候,首先需要你实现自己的包装器类,也就是一个切面,英文Aspect。

Aspect通常可以在原有方法执行前、执行后两个位置被回调,这个回调的位置就叫做”joinPoint连接点”,一般就是before和after两种。

目标Target

切面需要连接到1个或者N个方法,这些方法所处的对象叫做Target。

最终的效果是,当target对象的某些方法被调用时,切面对象可以被回调,从而实现”切入”的效果。

连接点JoinPoint

因为1个切面对象,可以被连接到不同类的不同方法,被大家共享使用。

所以当切面对象被回调的时候,需要告诉切面对象当前是哪个类对象的哪个方法被调用,这样切面对象可以获知这些信息,我称这些调用点的信息为JoinPoint。

代理Proxy

JAVA是一个强类型语言,切面的目标是包装Target的方法,从而可以在方法调用前后实现切面逻辑,但是切面又不需要我们改Target的方法实现,这里面是有什么黑科技么?

JAVA采用了一个朴素的思路,就是创造一个子类,让它继承Target类,这样子类覆写Target类的所有方法,在其中就可以引入切面回调:

JAVA通过库Cglib,可以实现在运行时根据指定的class动态生成一个子class,这个子class称为一个Proxy。

库Cglib允许我们注册一个回调函数到Proxy对象,每当Proxy的方法被调用就会委托给回调函数处理,我们可以在回调函数里实现切面的回调。

实践

下面会按上述理论概念,逐段呈现相关代码实现,因为原理都比较清晰,对照代码去理解即可。

流程

AspectTarget类是目标对象,我无需对它做任何代码修改,就可以将Aspect切面连接过去,从而实现在Target调用前后打印日志的效果。

因为Target需要连接切面,所以Target对象需要一个Proxy,Proxy对象是通过Cglib来动态生成的,AspectProxy是一个类似工厂的类。

切面Aspect抽象

切面连接到目标,同时切面的逻辑是业务自定义的,所以需要对切面做抽象:

一个切面接口有2个回调,分别在方法调用前和方法调用后,这是我对切面的一个基本抽象。

我实现了一个切面,在回调方法中打印出了连接点的信息,也就是当前是哪个方法调用引起的切面回调。

连接点JoinPoint

告知切面哪个方法被调用,这些都是基于运行时反射获得的信息,在下面会看到。

目标Target

既原有的类(被代理的类),只需将上面的切面连接到该类的aMethod和bMethod方法,而不需要对AspectTarget类进行任何修改。

代理 Proxy

该类用于生成Target的代理对象,通过buildTargetProxy函数获得生成代理类对象。

Enhancer是Cglib库的类,需要告诉它继承的基类(也就是target),再需要设置一个委托对象,每当proxy的方法被调用就会回调该对象的intercept方法。

在这里,我的AspectProxy对象自身就实现了intercept方法,所以enhancer.setCallback(this)即可。

既然enhancer的callback处理器就是AspectProxy自身,那么就需要在AspectProxy.intercept方法中实现切面的回调逻辑。

至于哪些切面要被回调,这需要我们主动向AspectProxy注册切面,由aspectMap维护这个关系。

当intercept回调时,method是AspectTarget对应被调用的方法反射,objects是调用参数,o就是enhancer生成的代理对象,methodProxy是代理对象被调用的方法反射,这几个比较绕,但是不必太在意。

我要做的就是,在真正调用父类(AspectTarget)的方法之前回调所有注册切面的before方法(同时传入连接点信息),在之后调用after方法,仅此而已。

效果

可见,Target的aMethod和bMethod都被同一个切面连接,调用时打印了日志。

而cMethod因为没有注册切面,所以没有打印日志。

参考文章

《Spring AOP的实现机制》

《Cglib的使用方法》

《Spring AOP 简介以及简单用法》

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~