【Spring深度分析】AOP技术 的基本实现

YouzgLogo

在本人之前的博文中,讲解了 Spring框架的 IoC/DI技术 的基本实现
那么,在我们之后的 Spring框架的学习中,还有一个技术十分 重要 —— AOP技术

在本篇博文中,本人将着重讲解 AOP技术的实现

基本知识点:

概念

Aspect-Oriented Programming(面向切面编程)
是OOP(即:Object-Oriented Programing —— 面向对象编程)的补充完善
即:不修改源代码 的情况下给程序 动态地统一地 添加功能

那么,为什么AOPOOP补充和完善呢?

  • OOP
    OOP引入封装继承多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合
    当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力
    也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系

例如日志功能
日志代码往往水平地散布所有对象层次中,
而与它所散布到的对象核心功能毫无关系
对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此
这种散布在各处的无关的代码被称为横切(cross-cutting)代码
在OOP设计中,它导致了大量代码的重复,而不利于 各个模块的重用


  • AOP
    而AOP技术则恰恰相反,
    它利用一种称为“横切”的技术,剖解开封装的对象内部
    并将那些影响了多个类的公共行为封装到一个可重用模块
    并将其名为“Aspect”,即方面
    所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用逻辑责任封装起来,
    便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可操作性可维护性
    AOP代表的是一个横向的关系:
    如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性行为,
    那么AOP的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息
    剖开的切面,也就是所谓的“方面”了
    然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹

实现AOP的技术,主要分为两大类:

  • 一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,
    以取代原有对象行为的执行;
  • 二是采用静态织入的方式,引入特定的语法创建“方面”,
    从而使得编译器可以在编译期间织入有关“方面”的代码

然而殊途同归,实现AOP的技术特性却是相同的,分别为:

  1. join point(连接点):
    是程序执行中的一个精确执行点,例如类中的一个方法
    它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。
  2. point cut(切入点):
    本质上是一个捕获连接点的结构
    在AOP中,可以定义一个point cut,来捕获相关方法的调用。
  3. advice(通知):
    是point cut的执行代码,是执行“方面”的具体逻辑。
  4. aspect(方面):
    point cut和advice合起来就是aspect,它类似于OOP中定义的一个类,
    但它代表的更多是对象间横向的关系。
  5. introduce(引入):
    为对象引入附加的方法或属性,
    从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。

上述的技术特性组成了基本的AOP技术,大多数AOP工具均实现了这些技术
它们也可以是研究AOP技术的基本术语


那么,在本篇博文中,本人就通过动态代理技术,来实现AOP技术的核心步骤

需求分析:

首先,我们来分析下实现AOP技术,有哪些需求:
需求分析

  1. 用户可以选择代理模式(两种代理模式:JDKCGLib);
  2. 用户可以选择类及方法,进行拦截
  3. 对于一个类同一个方法,允许形成拦截器链(栈)
  4. 允许“前置拦截”、“后置拦截”和“异常拦截”;
  5. 上述三种拦截可以只实现局部
  6. 前置拦截除完成用户指定功能外,允许用户终止方法的执行
  7. 第6点若发生终止情况,则,拦截器链应该终止
  8. 允许前置拦截更改参数值
  9. 允许后置拦截更改返回值
  10. 拦截器的拦截功能,应该由用户决定
  11. 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大信息:
  1. Class对象
  2. Method对象
  3. 参数类型 数组
  • 参数 数组
  • 返回值

现在,我们依据上述思想,给出如下代码:

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);
	}
	
}

那么,现在我们来通过上述的代码,实现下拦截器链的代码:

对于 拦截器链,我们需要知道如下信息:

  • 前置拦截器链
  • 后置拦截器链
  • 切入点
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


posted @ 2020-04-25 00:09  在下右转,有何贵干  阅读(99)  评论(0编辑  收藏  举报