1)AOP的初步认识
先给出关于AOP的专业术语的解释(来自百度百科):
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
我个人觉得,AOP技术,意为面向切面编程,这个技术是以代理机制为基础(有关代理机制的相关内容可以借鉴我前面的博客讲述),也就是在不对源代码进行更改的情况下给程序动态添加功能。
简单来说,在原本的程序执行过程中,可以“选择性”地在它之前或者之后进行拦截(这个“选择性”的具体意思会在下文给出说明),在原先的功能方法执行前后插入其他的东西进而完成一些其他的操作。
2)有关AOP的实现需求
那在展开讲述之前,我们需要明确上述一系列操作的具体需求是什么?即我们要完成的目标:
1、我们说AOP技术的基础是代理机制,而就我们前面说过的动态代理机制
有两种:JDK代理和CGLIB代理,到底用哪种代理模式来完成相关操作,这个应该由用户自己选择;
2、上面提到的“选择性”的意思是,对于某个方法可以进行拦截,也可以不进行拦截。也就是由用户选择要拦截的类和方法,即用户决定是否进行拦截;
3、如果用户决定要拦截的某个类中的某个方法,那结果是不一定只拦截一次就结束,它可以有选择的进行多次拦截。所以说,对于同一个类的同一个方法,可以多次拦截,即,可以形成一条拦截器链;
4、而用户决定拦截的某个类中的某个方法之后,到底要添加一些什么功能只有用户自己知道,即,进行拦截之后要做的事也由用户决定。
5、上面说到,在用户决定拦截某个方法之后,可以进行多次拦截,形成链式结构。但在这个拦截过程中,用户可以随时终止拦截,不一定要把拦截器链中的拦截全部执行完;
6、大体上,拦截可以分为前拦截、后拦截和异常拦截,即,可以在原本的方法执行之前或者之后进行拦截,也可以在执行该方法发生异常时进行拦截,而上述三种拦截不一定一次全部包括,在拦截时可以只完成其中一种;
7、允许前拦截更改方法的参数;
8、允许后拦截更改方法的返回值;
9、AOP尽量和IOC结合起来,代理对象一个默认从BeanFactory中获取。(选择实现)
3)有关拦截的相关内容
首先,要明确一个问题,即,方法和拦截器之间的关系:
是当某个方法被执行到时,去找到它对应的拦截器链
而方法的执行是通过代理完成的,前面讲过两种动态代理机制
,分别是JDK代理和CGLIB代理,前面说到AOP的需求时说到,用哪种代理模式可以由用户自己决定,而两种代理模式中,一般默认使用的是CGLIB代理,CGLIB代理模式不需要接口,使得它使用范围更广泛,但是用的广泛就随之会出现不好控制的情况,而JDK代理模式因为有接口的存在,使得要使用这种代理模式,就必须实现这个接口,看起来有点繁琐,但是使用接口使得系统更加的松耦合,也会在操作过程中好控制一些,所以AOP中的代理推荐大家使用DK代理机制。(当然,两种代理模式都可以实现最后的结果)
在这里我们先给出两个注解:
@Aspect注解,意思是切面,相当于类声明,通知所在的类
@Pointcut注解,意思是切入点,注解的属性是字符串,表示被拦截的方法;
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 value();
}
pointcut为切点的意思,也就是要拦截的地方,可以用这个注解表示要拦截的方法,为了使一个方法可以有多个不同的拦截的同时,一个拦截也可以用于不同的方法身上,这里我们用到了通配符的概念,用来同时匹配到多个方法,而@pointcut注解的字符串属性的表达式就是正则表达式(关于通配符和正则表达式在下面给出简要说明)。
而拦截器和方法之间的映射关系是,形成一个以@pointcut注解的字符串为键,对应的拦截器链为值形成一个Map,当扫描到类中的某个方法时,用method.toString()方法得到表示方法的字符串和Map中存放的@pointcut注解的字符串相比较,如果匹配成功的话,表明该方法存在对应的拦截,则找到它的拦截器链,执行里面的拦截。
注:由于注解必须要有所依赖(即,注解要有它发挥作用的目标存在),例如方法,成员,类等等,所以在这里我们给出一个空的方法来承载@pointcut注解:
@Pointcut("execution(* *.targetMethod(..))")
public void point() {}
上述代码表示,要拦截的方法是任意类下的任意返回值并且任意参数的targetMethod()方法,* .targetMethod(…)这一部分叫做正则表达式,里面的符号就是我们知道的通配符。
注:通配符和正则表达式的简单说明
1、通配符是一种特殊语句,主要有星号(*)和问号(?),用来模糊搜索文件。当查找文件夹时,可以使用它来代替一个或多个真正字符;当不知道真正字符或者懒得输入完整名字时,常常使用通配符代替一个或多个真正的字符。
2、正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。
为了说明情况,举例给出相关的@Before注解的方法:
@Before(value="point()")
public void before1() {
System.out.println("执行无参情况的拦截器!");
}
上述代码表示,该拦截器方法before1()所对应的执行方法就是前面@pointcut注解的字符串表示的方法,即,任意类中的任意返回值的任意参数的targetMethod()方法都对应before1拦截方法。
在具体表达被拦截的类和方法的时候,就需要考虑到拦截器本身,也就是,应该在完成拦截器的同时,说明其拦截的方法。
在执行方法和拦截之间需要一个连接点(JoinPoint)
意思是说,当执行某个方法时,需要进行拦截操作,那么前后拦截应该在原本要执行方法的参数或者返回值的基础上进行,也就是满足了前面我们所说的拦截需求“允许前拦截更改参数和允许后拦截更改返回值”。因此,我们给一个类JoinPoint,专门针对方法的参数args和返回值result做封装,以便执行拦截方法的时候可以通过JoinPoint来获取原本方法的参数和返回值。(JoinPoint类中可能还会有方法,参数类型等成员,但主要的是参数和返回值)
import java.lang.reflect.Method;
public class JoinPoint {
private Object[] args;
private Object result;
public JoinPoint() {
}
public void setResult(Object result) {
this.result = result;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
public Object getResult() {
return result;
}
}
拦截器方法除了上述的无参情况之外,还可能存在单参,参数是JoinPoint类型,以及全参,也就是拦截器方法的参数都可以在原本的方法参数中找到的情况,简单举例:
@Before(value="point()")
public void before2(JoinPoint join) {
System.out.println("执行参数为JoinPoint类型的拦截器!");
}
@Before(value="point()")
public void before3(Object args1,Object args2) {
System.out.println("执行全参情况的拦截器!");
}
那么针对这三种情况给出相应的处理方法:
private Object[] getParaValues() {
Object[] paraValues = new Object[] {};
int argsCount = method.getParameterCount();
if(argsCount > 0) {
Class<?>[] paraTypes = method.getParameterTypes();
JoinPoint joinPoint = interceptLink.getJoinPoint();
Object[] argsValue = joinPoint.getArgs();
if(argsCount == 1 && paraValues[0].equals(JoinPoint.class)) {
paraValues = new Object[] {joinPoint};
} else {
paraValues = new Object[argsCount];
Class<?>[] methodParaTypes = method.getParameterTypes();
for(int index = 0 ; index < argsCount ; index ++) {
if(!paraTypes[index].equals(methodParaTypes[index])) {
continue;
}
paraValues[index] = argsValue[index];
}
}
}
return paraValues;
}
在这里,拦截器方法的执行是通过反射机制来进行的;
第一种情况,无参:拦截器方法没有参数时,直接new出一个空的 Object类型的数组,把这当成方法执行的参数,反射执行拦截方法;
第二种情况,单参(JoinPoint 类型):通过method得到拦截方法的所有参数,比较参数个数是否为1,并且看这一个参数是不是JoinPoint 类型的,用JoinPoint 的对象生成一个Object类型的数组作为方法执行的参数;
第三种情况,全参:在拦截器方法有参数的情况下,遍历所有的参数,依次将原本方法的参数赋值给拦截器方法的参数,得到具体的参数数组作为方法执行的参数;
@Override
public boolean before() {
Object result = null;
try {
Object[] paraValues = getParaValues();
result = method.invoke(object, paraValues);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
if(result != null
&& method.getReturnType().equals(boolean.class)) {
return (boolean)result;
}
return true;
}
4)前拦截和后拦截的实现
根据上面的讲述,根据要执行的方法,寻找对应的拦截器链,而这个拦截器链是将前置拦截和后置拦截封装起来的一个整体(还包括异常拦截,这里不对异常拦截作详细说明)。
public class InterceptLink {
private JoinPoint joinPoint;
private BeforeInterceptLink beforeLink;
private AfterInterceptLink afterLink;
public InterceptLink() {
this.beforeLink = null;
this.afterLink = null;
}
public JoinPoint getJoinPoint() {
return joinPoint;
}
public void setJoinPoint(JoinPoint joinPoint) {
this.joinPoint = joinPoint;
}
public void addBeforeIntercepter(IBefore before) {
if(beforeLink == null) {
beforeLink = new BeforeInterceptLink();
beforeLink.setInterceptLink(this);
}
beforeLink.addBeforeIntercepter(before);
}
public void addAfterIntercepter(IAfter after) {
if(afterLink == null) {
afterLink = new AfterInterceptLink();
}
afterLink.addAfterIntercepter(after);
}
public boolean before() {
if(beforeLink == null) {
return true;
}
return beforeLink.bofore();
}
public Object after(Object result) {
if(afterLink == null) {
return result;
}
return afterLink.after(result);
}
public Object[] getArgs() {
return joinPoint.getArgs();
}
public void setArgs(Object[] args) {
joinPoint.setArgs(args);
}
public void setResult(Object result) {
joinPoint.setResult(result);
}
public Object getResult() {
return joinPoint.getResult();
}
}
其中的前置拦截和后置拦截都是链式结构,或者说链表,是由一个一个的节点组成的,下面分别说明前置拦截和后置拦截:
前置拦截
首先,有一个前置拦截的接口,这个接口中定义的方法就是具体的前置拦截的操作,这个方法 的内容由用户在之后完成;
public interface IBefore {
boolean before();
}
前置拦截链是由一个一个的前置拦截节点组成的,某一个节点都代表一次不同的拦截操作,看代码:
public class BeforeInterceptNode {
private IBefore before;
private BeforeInterceptNode next;
public BeforeInterceptNode() {
this.next = null;
}
public BeforeInterceptNode(IBefore before, BeforeInterceptLink beforeLink) {
this.before = before;
this.next = null;
}
public void setBefore(IBefore before) {
this.before = before;
}
public void addBeforeNode(BeforeInterceptNode node) {
if(next == null) {
next = node;
return;
}
next.addBeforeNode(node);
}
public boolean doBefore() {
boolean ok = before.before();
if(!ok) {
return ok;
}
if(next != null) {
return next.doBefore();
}
return ok;
}
}
结合上述代码,每个前置拦截器节点大体上都存在两部分操作,针对本节点进行拦截操作,以及进行节点的增加,也就是用户可以决定进行多次拦截。
前置拦截在执行上是顺势执行,也就是一旦某个方法需要前拦截了,就顺着执行前置拦截器链中的所有前拦截(当然,用户可以随时终止拦截),每次执行完一个拦截,就看在它之后还有没有前拦截,有的话继续执行,直到全部的前拦截都执行完,或者是用户终止了拦截,就结束拦截,进而接着执行原本的方法。
所有的前置拦截节点组成的前置拦截器链如下:
public class BeforeInterceptLink {
private BeforeInterceptNode head;
private InterceptLink interceptLink;
public BeforeInterceptLink() {
this.head = null;
}
public void addBeforeIntercepter(IBefore before) {
BeforeInterceptNode node = new BeforeInterceptNode(before, this);
if(head == null) {
head = node;
} else {
head.addBeforeNode(node);
}
}
public boolean bofore() {
if(head == null) {
return true;
}
return head.doBefore();
}
public void setInterceptLink(InterceptLink interceptLink) {
this.interceptLink = interceptLink;
}
public Object[] getArgs() {
return interceptLink.getArgs();
}
}
后置拦截
类似于前置拦截,有一个后置拦截的接口,这个接口中定义的方法就是具体的后置拦截的操作,这个方法 的内容由用户在之后完成;
public interface IAfter {
Object after(Object result);
}
后置拦截链是由一个一个的后置拦截节点组成的,某一个节点都代表一次不同的拦截操作,看代码:
public class AfterInterceptNode {
private IAfter after;
private AfterInterceptNode next;
public AfterInterceptNode() {
this.next = null;
}
public AfterInterceptNode(IAfter after) {
this.after = after;
this.next = null;
}
public void setAfter(IAfter after) {
this.after = after;
}
public void addAfterNode(AfterInterceptNode node) {
if(next == null) {
next = node;
return;
}
next.addAfterNode(node);
}
public Object doAfter(Object result) {
if(next == null) {
return after.after(result);
}
result = next.doAfter(result);
return after.after(result);
}
}
结合上述代码,也是类似于前置拦截,每个后置拦截器节点大体上都存在两部分操作,针对本节点进行拦截操作,以及进行节点的增加,也就是用户可以决定进行多次拦截。
与前置拦截不同的是,后置拦截在执行上是逆势执行,也就是一旦用户决定某个方法需要被拦截了,就倒着执行后置拦截器链中的所有前拦截(当然,用户可以随时终止拦截),什么叫做倒着执行?就是说,在后置拦截器链中遇到第一个后置拦截时,先不直接执行(先攒着),查看它后面还有没有后置拦截存在,如果有的话依旧不执行拦截,继续向后查看,一直到了最后一个后置拦截的时候,再开始执行拦截操作,执行完依次向前推送,直到第一个后置拦截执行完,或者用户终止了拦截,就结束拦截操作。
注:前置拦截和后置拦截的执行过程就跟括号匹配差不多,括号最里面的内容相当于原本要执行的方法,内容前面的所有括号(前括号)就是前置拦截,执行的时候需要从第一个开始一直到最后一个结束,而内容后面的括号(后括号)就是后置拦截,执行的时候需要从最后一个开始执行,直到第一个结束。
所有的后置拦截节点组成的后置拦截器链如下:
public class AfterInterceptLink {
private AfterInterceptNode head;
public AfterInterceptLink() {
this.head = null;
}
public void addAfterIntercepter(IAfter after) {
AfterInterceptNode node = new AfterInterceptNode(after);
if(head == null) {
head = node;
return;
}
head.addAfterNode(node);
}
public Object after(Object result) {
if(head == null) {
return result;
}
return head.doAfter(result);
}
}
上述只是我通过简单示例来说明AOP的部分内容,有关AOP的深入理解会在之后完善。