【Spring深度分析】AOP技术 的基本实现
在本人之前的博文中,讲解了 Spring框架的 IoC/DI技术 的基本实现
那么,在我们之后的 Spring框架的学习中,还有一个技术十分 重要 —— AOP技术
在本篇博文中,本人将着重讲解 AOP技术的实现
基本知识点:
概念:
Aspect-Oriented Programming(面向切面编程)
是OOP(即:Object-Oriented Programing —— 面向对象编程)的补充和完善
即:不修改源代码 的情况下给程序 动态地、统一地 添加功能
那么,为什么说AOP是OOP的补充和完善呢?
- OOP
OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合
当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力
也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系例如日志功能
日志代码往往水平地散布在所有对象层次中,
而与它所散布到的对象的核心功能毫无关系
对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此
这种散布在各处的无关的代码被称为横切(cross-cutting)代码,
在OOP设计中,它导致了大量代码的重复,而不利于 各个模块的重用
- AOP
而AOP技术则恰恰相反,
它利用一种称为“横切”的技术,剖解开封装的对象内部,
并将那些影响了多个类的公共行为封装到一个可重用模块,
并将其名为“Aspect”,即方面
所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,
便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性
AOP代表的是一个横向的关系:
如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为,
那么AOP的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息
而剖开的切面,也就是所谓的“方面”了
然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹
实现AOP的技术,主要分为两大类:
- 一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,
以取代原有对象行为的执行;- 二是采用静态织入的方式,引入特定的语法创建“方面”,
从而使得编译器可以在编译期间织入有关“方面”的代码
然而殊途同归,实现AOP的技术特性却是相同的,分别为:
- join point(连接点):
是程序执行中的一个精确执行点,例如类中的一个方法
它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。- point cut(切入点):
本质上是一个捕获连接点的结构
在AOP中,可以定义一个point cut,来捕获相关方法的调用。- advice(通知):
是point cut的执行代码,是执行“方面”的具体逻辑。- aspect(方面):
point cut和advice合起来就是aspect,它类似于OOP中定义的一个类,
但它代表的更多是对象间横向的关系。- introduce(引入):
为对象引入附加的方法或属性,
从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。
上述的技术特性组成了基本的AOP技术,大多数AOP工具均实现了这些技术
它们也可以是研究AOP技术的基本术语
那么,在本篇博文中,本人就通过动态代理技术,来实现AOP技术的核心步骤:
需求分析:
首先,我们来分析下实现AOP技术,有哪些需求:
需求分析:
- 用户可以选择代理模式(两种代理模式:JDK和CGLib);
- 用户可以选择类及方法,进行拦截;
- 对于一个类的同一个方法,允许形成拦截器链(栈);
- 允许“前置拦截”、“后置拦截”和“异常拦截”;
- 上述三种拦截可以只实现局部;
- 前置拦截除完成用户指定功能外,允许用户终止方法的执行;
- 第6点若发生终止情况,则,拦截器链应该终止;
- 允许前置拦截更改参数值;
- 允许后置拦截更改返回值;
- 拦截器的拦截功能,应该由用户决定;
- AOP功能应该和IoC功能合并;
即,默认从BeanFactory中获取代理对象;
标志注解:
因为我们要区分给目标类增加前置拦截还是后置拦截,亦或是 异常拦截,
因此,本人在这里先来给出三个注解,来标识要给目标方法增加怎样的拦截:
因为我们要在注解中声明用哪个方法进行拦截,
所以在注解中增加一个name属性,来标识拦截方法
@Before注解:
package edu.youzg.aop.core;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(METHOD)
public @interface Before {
String name();
}
@After注解:
package edu.youzg.aop.core;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(METHOD)
public @interface After {
String name();
}
@ThrowException注解:
package edu.youzg.aop.core;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(METHOD)
public @interface ThrowException {
String name();
}
被上述三个注解标志的方法,
应该被当成拦截器中的对应拦截
根据要执行的方法,寻找拦截器链;
但是,若是我们将前置拦截、后置拦截、异常拦截都分开的话,是非常难实现的
因此,本人有这样的思路:
不分开前置、后置等拦截器链,封装到一起
将 拦截器链 构造成:
前置拦截 —> 被拦截的方法 —> 后置拦截
现在,本人来给出一个拦截器接口,来规范我们的拦截器所要完成的功能:
IIntercepter 接口:
package edu.youzg.aop.core;
public interface IIntercepter {
boolean before(Object object, Object[] args); //前置方法拦截
Object after(Object result); //后置方法拦截
}
拦截器链:
在本人上述的讲解中,我们知道了,我们需要给要被拦截的方法一个拦截器链,
- 前置拦截 实行对参数的筛选判别:
在前置拦截中,我们可以通过影响参数、筛选参数,以及新增一些操作
来对原方法产生影响- 后置拦截 实行对返回值的筛选判别:
在后置拦截中,我们可以通过影响返回值、筛选返回值,以及新增一些操作
来对原方法的结果产生影响- 异常拦截 实行对异常的筛选判别:
在异常拦截中,我们可以通过筛选异常、处理资源释放,以及新增一些操作
来对原方法的结果产生影响
那么,由于本篇博文的主要目的是来基本实现AOP技术,
来带同学们深度挖掘Spring框架的实现原理,
因此,在拦截器链的实现过程中,本人仅实现前置拦截与后置拦截
根据本人上面 的讲解,我们能够知道:
我们需要标识哪个类的哪个方法需要拦截,
所以,本人给出 @PointCut注解:
“切入点” —— @PointCut注解:
因为我们可能在“扫描”到切入点后,将切入点的信息存储到一个集合中以便处理,
所以我们在此注解中增加一个name属性
package edu.youzg.aop.core;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(METHOD)
public @interface PointCut {
String name();
}
对于拦截器链,我们要明白“切入点”方法的信息,
因此,本人先来给出一个JoinPoint类,来封装要进行拦截的方法:
“切入点信息” —— JoinPoint类:
对于“切入点”方法,我们需要知道如下信息:
- 反射机制获取该方法,所需3大信息:
- Class对象
- Method对象
- 参数类型 数组
- 参数 数组
- 返回值
现在,我们依据上述思想,给出如下代码:
package edu.youzg.aop.core;
import java.lang.reflect.Method;
public class JoinPoint {
private Class<?> klass;
private Method method;
private Class<?>[] paraTypes;
private Object[] args;
private ReturnValue result;
public JoinPoint() {
result = new ReturnValue();
}
public ReturnValue getReturnValue() {
return result;
}
public Class<?> getKlass() {
return klass;
}
public Method getMethod() {
return method;
}
Class<?>[] getParaTypes() {
return paraTypes;
}
void setKlass(Class<?> klass) {
this.klass = klass;
}
void setMethod(Method method) {
this.method = method;
}
void setParaTypes(Class<?>[] paraTypes) {
this.paraTypes = paraTypes;
}
void setArgs(Object[] args) {
this.args = args;
}
void setResult(Object result) {
this.result.setReturnType(result.getClass());
this.result.setValue(result);
}
public Object[] getArgs() {
return args;
}
public Object getResult() {
return result;
}
}
对于前置拦截,我们关心的是:参数是否符合标准,
那么,本人先来给出一个接口,用于封装某一前置拦截节点所要执行的方法:
IBefore接口:
package edu.youzg.aop.core;
public interface IBefore {
boolean before();
}
那么,有了“切入点”信息 以及 IBefore接口,我们现在来思考下前置拦截所需类及方法:
本人一直在强调,拦截方法组成的“链”,
这也就是说:拦截器链(无论 前置拦截、后置拦截 或是 异常拦截)采用的是 “链式结构”
那么,在本人《数据结构与算法》专栏中,所讲解的链表的相关博文中,
讲到过:每一个链,都是由节点组成的
因此,我们先来编写 前置拦截节点:
前置拦截节点 —— IntercepterBeforeNode类:
package edu.youzg.aop.core;
public class IntercepterBeforeNode {
private IBefore before; //用于执行前置拦截方法
private IntercepterBeforeNode next; //用于指向下一个前置拦截节点
IntercepterBeforeNode() {
this.next = null;
}
IntercepterBeforeNode(BeforeIntercepterLink link, IBefore before) {
this.before = before;
this.next = null;
}
void setBefore(IBefore before) {
this.before = before;
}
/**
* 在该节点后 增添 新前置拦截节点,此处可以参考“链表”增添新节点的处理方法
* 注意:此处的增添,是“尾随增添”,这影响着我们之后的 后置拦截节点的增添
* 本人将在后面的内容中进行讲解
*/
void addNode(IntercepterBeforeNode node) {
if (next == null) {
next = node;
return;
}
next.addNode(node);
}
/**
* 执行 前置拦截方法
*/
boolean doBefore() {
boolean ok = before.before(); //调用 该节点的 前置拦截方法
if (!ok) { //若返回false,则表明 参数不符合要求,由于链式结构,后续代码不再进行
return ok;
}
if (next != null) { //若ok为true,则调用下一个前置拦截节点的 前置拦截方法
return next.doBefore();
}
return ok; //将 最后一个前置拦截节点 的处理结果 返回给 拦截器链
}
}
那么,在处理好前置拦截节点后,我们就来处理前置拦截链
前置拦截链 —— BeforeIntercepterLink类:
作为一个“链”,我们需要两点信息:
- 头节点 信息
- 拦截器链 信息
同时,我们还需要有 增添新节点、执行拦截器链的所有拦截方法的功能
因此,本人来给出 前置拦截器链 的代码:
package edu.youzg.aop.core;
public class BeforeIntercepterLink {
private IntercepterLink intercepterLink;
private IntercepterBeforeNode head;
public BeforeIntercepterLink() {
this.head = null;
}
void setIntercepterLink(IntercepterLink intercepterLink) {
this.intercepterLink = intercepterLink;
}
/**
* 获取 参数数组,以便我们的 前置拦截方法 进行“筛选”
*/
Object[] getArgs() {
return intercepterLink.getArgs();
}
/**
* 增添 前置拦截节点
*/
void addBeforeIntercepter(IBefore before) {
IntercepterBeforeNode node =
new IntercepterBeforeNode(this, before);
if (head == null) {
head = node;
} else {
head.addNode(node);
}
}
/**
* 执行 前置拦截器链的 所有拦截方法
*/
boolean before() {
if (head == null) {
return true;
}
return head.doBefore();
}
}
那么,前置拦截器链的内容就讲解完了
下面,本人按照上述思想,来给出后置拦截链的代码:
首先是 用于保存后置拦截方法的IAfter接口:
IAfter接口:
package edu.youzg.aop.core;
public interface IBefore {
boolean before();
}
后置拦截节点 —— IntercepterBeforeNode类:
在编写后置拦截节点前,本人先来提出一点:
在本人上面讲解的 前置拦截链 中,我们得知:前置拦截器链的拦截方法,是顺序调用的
那么,由于相应的 前置拦截方法与后置拦截方法可能存在逻辑,如下图:
拦截器1的前置拦截方法和后置拦截方法可能会存在成对的代码逻辑
而在上面的讲解中,我们设置了前置拦截器链的拦截方法执行顺序是 顺序执行
那么,为了对应代码逻辑,后置拦截链的拦截方法执行顺序就得是冒泡序:
那么,依据上面的思想,我们来继续代码的编写:
后置拦截节点 —— IntercepterBeforeNode类:
package edu.youzg.aop.core;
public class IntercepterAfterNode {
private IAfter after; //用于执行后置拦截方法
private IntercepterAfterNode next; //用于指向下一个后置拦截节点
IntercepterAfterNode() {
this.next = null;
}
void setAfter(IAfter after) {
this.after = after;
}
/**
* 在该节点后 增添 新后置拦截节点,此处可以参考“链表”增添新节点的处理方法
*/
void addNode(IntercepterAfterNode node) {
if (next == null) {
this.next = node;
return;
}
next.addNode(node);
}
/**
* 执行 后置拦截方法
*/
Object doAfter(Object result) {
if (next == null) { //若无后置节点,则执行该节点的 后置拦截方法(这是因为前置拦截方法是“顺序执行”的,为了“复合逻辑”,后置拦截节点需要“冒泡执行”)
return after.after(result);
}
// 先将 最后的后置节点 的拦截方法执行后,再“冒泡执行”前面的 拦截方法
result = next.doAfter(result);
result = after.after(result);
return result; //将结果返回给调用该拦截器链的用户
}
}
现在,本人来给出后置拦截链 —— AfterIntercepterLink类:
后置拦截链 —— AfterIntercepterLink类:
package edu.youzg.aop.core;
public class AfterIntercepterLink {
private IntercepterAfterNode head; //后置拦截器链 头节点
AfterIntercepterLink() {
this.head = null;
}
/**
* 增添 后置拦截节点
*/
public void addAfterIntercepter(IAfter after) {
IntercepterAfterNode node = new IntercepterAfterNode();
node.setAfter(after);
if (head == null) {
head = node;
return;
}
head.addNode(node);
}
/**
* 执行 后置拦截链的 拦截方法
*/
public Object after(Object result) {
if (head == null) {
return result;
}
return head.doAfter(result);
}
}
那么,现在我们来通过上述的代码,实现下拦截器链的代码:
拦截器链 —— IntercepterLink:
对于 拦截器链,我们需要知道如下信息:
- 前置拦截器链
- 后置拦截器链
- 切入点
package edu.youzg.aop.core;
public class IntercepterLink {
private JoinPoint joinPoint;
private BeforeIntercepterLink beforeLink;
private AfterIntercepterLink afterLink;
public IntercepterLink() {
this.beforeLink = null;
this.afterLink = null;
}
void setJoinPoint(JoinPoint joinPoint) {
this.joinPoint = joinPoint;
}
JoinPoint getJoinPoint() {
return joinPoint;
}
//设置前置拦截器链
public void addBeforeIntercepter(IBefore before) {
if (beforeLink == null) {
beforeLink = new BeforeIntercepterLink();
beforeLink.setIntercepterLink(this);
}
beforeLink.addBeforeIntercepter(before);
}
//设置后置拦截器链
public void addAfterIntercepter(IAfter after) {
if (afterLink == null) {
afterLink = new AfterIntercepterLink();
}
afterLink.addAfterIntercepter(after);
}
//执行 前置拦截器链 的 所有拦截方法
boolean before() {
if (beforeLink == null) {
return true;
}
return beforeLink.before();
}
//执行 后置拦截器链 的 所有拦截方法
Object after(Object result) {
if (afterLink == null) {
return result;
}
return afterLink.after(result);
}
Object[] getArgs() {
return joinPoint.getArgs();
}
public void setArgs(Object[] args) {
joinPoint.setArgs(args);
}
public Object getResult() {
return joinPoint.getResult();
}
public void setResult(Object result) {
joinPoint.setResult(result);
}
}
在进行编写 组织拦截器链的所有方法调用 的类 之前,
本人先来编写一个IIntercepter接口,
用于我们去调用 前置拦截和后置拦截方法:
调用前置拦截和后置拦截方法 —— IIntercepter接口:
package edu.youzg.aop.core;
public interface IIntercepter {
boolean before(Object object, Object[] args);
Object after(Object result);
}
现在,本人先来给出一个MethodInvoker类,
来封装一个方法通过反射机制调用:
执行拦截器链 —— MethodInvoker 类:
让我们仔细分析一下:
该类 有三个必须成员,
以及相应的setter(),
以便我们之后在此类中增添调用指定方法的方法:
private Object object;
private Method method;
private Object[] paras;
public void setObject(Object object) {
this.object = object;
}
public void setMethod(Method method) {
this.method = method;
}
public void setParas(Object[] paras) {
this.paras = paras;
}
但是,AOP技术,要对于方法进行拦截,
所以,在该类中,我们还需要上面所讲解的IIntercepter 接口:
private IIntercepter intercepter;
public void setIntercepter(IIntercepter intercepter) {
this.intercepter = intercepter;
}
那么,现在我们来完成下对指定方法实行拦截并执行的方法:
public Object methodInvoke() throws Throwable {
//我们先来给出一个“拦截器链”对象
IntercepterLink intercepterLink = new IntercepterLink();
// 上述intercepterLink应该是根据当前methd,
// 在“拦截器映射”中找到的,而不是现在这样new出来的!
JoinPoint joinPoint = new JoinPoint();
joinPoint.setKlass(object.getClass());
joinPoint.setMethod(method);
joinPoint.setParaTypes(method.getParameterTypes());
joinPoint.setArgs(paras);
intercepterLink.setJoinPoint(joinPoint);
if (intercepter != null) {
boolean ok = intercepter.before(object, paras);
if (!ok) { //如果前置拦截器链的拦截方法中,参数没有通过“筛选”,则返回null不再执行后续拦截器链的方法
return null;
}
}
Object result = method.invoke(object, paras); //执行 被拦截的方法
joinPoint.setResult(result); //将该方法的返回值设置进“切入点”对象中
if (intercepter != null) {
result = intercepter.after(result);
}
// 这里的代码应该由想要实现AOP功能的程序员来编写
return result;
}
那么,本人来整合下 MethodInvoker 类 的完整代码:
package edu.youzg.aop.core;
import java.lang.reflect.Method;
public class MethodInvoker {
private Object object;
private Method method;
private Object[] paras;
private IIntercepter intercepter;
public MethodInvoker(Object object, Method method, Object[] paras) {
this.object = object;
this.method = method;
this.paras = paras;
}
public void setIntercepter(IIntercepter intercepter) {
this.intercepter = intercepter;
}
public void setObject(Object object) {
this.object = object;
}
public void setMethod(Method method) {
this.method = method;
}
public void setParas(Object[] paras) {
this.paras = paras;
}
public Object methodInvoke() throws Throwable {
// 这里的代码应该由想要实现AOP功能的程序员来编写
IntercepterLink intercepterLink = new IntercepterLink();
// 上述intercepterLink应该是根据当前methd,
// 在拦截器映射中找到的,而不是现在这样new出来的!
JoinPoint joinPoint = new JoinPoint();
joinPoint.setKlass(object.getClass());
joinPoint.setMethod(method);
joinPoint.setParaTypes(method.getParameterTypes());
joinPoint.setArgs(paras);
intercepterLink.setJoinPoint(joinPoint);
if (intercepter != null) {
boolean ok = intercepter.before(object, paras);
if (!ok) {
return null;
}
}
Object result = method.invoke(object, paras);
joinPoint.setResult(result);
if (intercepter != null) {
result = intercepter.after(result);
}
// 这里的代码应该由想要实现AOP功能的程序员来编写
return result;
}
}
现在,本人来给出一个 用于选择并调用 动态代理 的 拦截方法 的 小工具:
动态代理小工具:
请关注本人博文 —— 《MecProxy 的实现》
那么,最后,本人再来给出一个用于封装要被拦截的方法的返回值的类:
封装返回值 —— ReturnValue类c:
package edu.youzg.aop.core;
public class ReturnValue {
private Class<?> returnType;
private Object value;
public ReturnValue() {
}
Class<?> getReturnType() {
return returnType;
}
void setReturnType(Class<?> returnType) {
this.returnType = returnType;
}
Object getValue() {
return value;
}
void setValue(Object value) {
this.value = value;
}
}
实现逻辑:
那么,现在我们只需要建立映射关系:
通过上文中所讲解的@PointCut注解来标志需要拦截的方法,
再通过扫描 @Bean注解、@After注解以及@ThrowException注解 来形成映射关系
之后通过本人之前博文中所讲解的“包扫描技术”,
用JoinPoint类来封装该方法的信息,
将指定的拦截方法存入适当的拦截器链处,
生成相应的拦截器链,
再通过上文中所讲解的 MethodInvoker 类,调用拦截器链的方法,
再将结果封装到ReturnValue类中,进行后续处理
按照如上步骤,最后我们就能够实现 APO机制 的注解方式了
基本实现代码:
需要上述全部代码的同学,请点击下方链接:
AOPImpl