Spring的AOP面向切面编程的核心在于对于一个切点的执行过程可以进行干预,加入前置拦截、后者拦截以及异常处理等,其中的底层原理是使用代理机制得到一个代理对象,实现方法的代理调用,以及通过给切面的切点添加拦截器链,在参考了SpringAOP的一系列源码后,简单模拟实现了面向切面编程的几个重要部分,如下基本上分为三个部分:
1、代理对象的获取
2、切点的匹配以及拦截器链的实现
3、对代理对象执行方法的参数以及结果的获取与处理
下面对这三个方面的实现进行具体阐述。
1、代理对象的获取
对于代理对象的获取,我采用了两种实现方式,一种是必须基于接口的JDKProxy,另一种是不需要接口,使用子类继承的方式实现代理的CGLib代理机制。并且将二者整合为一个LWProxy,提供给用户选择使用何种代理机制来实现代理。
对于两种代理机制的实现与区别在之前的 文章已经叙述,在此不作过多阐述。这里实现的代理机制要添加一个MethodInvoker成员来供使用者添加拦截器,且提供set方法供两者整合的LWProxy类使用,注入同一个MethodInvoker对象。具体代码及注释如下:
/**
* @author shkstart
* @create 2022-06-20 21:55
* 提供给用户使用的Proxy类,包含了JDK与CGLib
*/
public class LWProxy {
//用来标识何种代理机制
public static final int CGLIB_PROXY = 1;
public static final int JDK_PROXY = 0;
private MethodInvoker methodInvoker;
private int proxyChoice;
//初始化methodInvoker对象,供赋值给代理对象的methodInvoker成员
public LWProxy() {
this.methodInvoker = new MethodInvoker();
this.proxyChoice = JDK_PROXY;
}
//给定方式得到代理对象
public LWProxy(int proxyChoice) {
this();
//如果给的值无效,则默认JDK代理机制
if (proxyChoice != CGLIB_PROXY && proxyChoice != JDK_PROXY) {
this.proxyChoice = JDK_PROXY;
return;
}
this.proxyChoice = proxyChoice;
}
//也允许设置代理机制的选择
public void setProxyChoice(int proxyChoice) {
if (proxyChoice != CGLIB_PROXY && proxyChoice != JDK_PROXY) {
this.proxyChoice = JDK_PROXY;
return;
}
this.proxyChoice = proxyChoice;
}
//添加拦截器链
public void addIntercept(String methodName, Intercept intercept) {
this.methodInvoker.addIntercept(methodName, intercept);
}
public <T> T getProxy(Object object) {
if (this.proxyChoice == CGLIB_PROXY) {
CGLIbProxy cglIbProxy = new CGLIbProxy();
cglIbProxy.setMethodInvoker(this.methodInvoker);
return cglIbProxy.getProxy(object);
} else {
JDKProxy jdkProxy = new JDKProxy();
jdkProxy.setMethodInvoker(this.methodInvoker);
return jdkProxy.getProxy(object);
}
}
public <T> T getProxy(Class<?> klass) {
try {
return getProxy(klass.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2、切点的匹配以及拦截器链的实现
由于想匹配到切点就必须提供方法的全类名以及参数的全类名,对于使用者来说是极大的不便利,于是我采用了正则表达式的处理方式来匹配切点。用户只需要提供一个普通的类名、或方法名及参数,对于其他的不确定的地方使用‘*’来代替,当用户调用到添加拦截器时,对于用户传入的简单切点名进行转化,MethodInvoker类中提供了处理用户提交的普通的类名与参数名的toRegex方法,此方法把简单的字符串转换为正则表达式,从而找到符合的切面的切点。完成切点的匹配。
/**
* 用户提供切点名,*.ClassName.methodName(*)
* 例:*.MyInterface.*(*String,*int) 表示任意长度的前一个字符,前面必须有字符
* 故 .* 表示任意字符
* 用户只需输入简单的参数以及方法名,不确定的地方用*代替
* .*IMyInterface..*\(.*String,.*int\)
*
*/
private String toRegex(String methodName) {
StringBuffer stringBuffer = new StringBuffer();
//将不确定的地方替换为.*表示匹配任意字符
String buffer = methodName.replace("*", ".*");
int left =buffer.indexOf("(");
int right = buffer.indexOf(")");
//得到类名与方法名
stringBuffer.append(".*").append(buffer.substring(0, left)).append("\\(");
//按照逗号分割出参数字符串数组
String[] args = buffer.substring(left + 1, right).split(",");
for(int i = 0; i < args.length; i++) {
stringBuffer.append((i == 0 ? "" : ",")).append(".*").append(args[i]);
}
stringBuffer.append("\\)");
return stringBuffer.toString();
}
拦截器链的实现略复杂一些,定义它的两个成员为intercept和interceptChain,前置为拦截器,后者指向了下一个拦截器。他们的关系如图所示: 而对于这种拦截器链的添加拦截器使用递归的方式,将所有加入拦截器的情况涵盖进来,总体可分为三类。
1)、添加此拦截器链的第一个拦截器,此时拦截器链的intercept与interceptChain均为null,可以直接赋值给intercept,然后返回。
2)、存在一个拦截器后的添加,此时interceptChain = null,则我们new 一个拦截器对象,赋值给interceptChain ,且调用新的拦截器链的添加拦截器方法。可以到达情况一,赋值后返回。
3)、存在多个拦截器后的添加,此时intercept与interceptChain 均不为null,故需要找到最后一个拦截器所在的拦截器链,即找到第二种情况,最后再转化到情况一,执行赋值操作。
代码实现如下:
//递归添加拦截器链
public void addIntercept(String cutPoint, Intercept intercept) {
//添加第一个拦截器,或者拦截器链的最后一个拦截器的添加,每次真正的添加
if (this.intercept == null) {
this.cutPoint = cutPoint;
this.intercept = intercept;
return;
}
//到达拦截器链的最后一个拦截器
if (this.interceptChain == null) {
this.interceptChain = new InterceptChain();
this.interceptChain.addIntercept(cutPoint, intercept);
return;
}
//递归找到最后一个拦截器
this.interceptChain.addIntercept(cutPoint, intercept);
}
拦截器链创建完毕后,该考虑不同拦截的不同调用顺序,即前置拦截与后置拦截不应该采用同一种执行顺序,我采用了不同的实现方法使得前置拦截的执行顺序为拦截器添加的顺序,而后置拦截的顺序为添加的顺序的倒序,类似于SpringMVC中对于拦截器的处理。
前置拦截器(下沉执行):采用由浅入深的方式,由拦截器链作为判断条件递归执行,
实现如下:
//下沉执行前置拦截
public boolean doBefore(Method method) {
//一旦存在前置拦截则不执行直接返回为false
//可以嵌套的原因是当拦截器为null时,拦截器链必定为null
if (this.intercept != null) {
if (Pattern.matches(cutPoint, method.toString())) {
if (!this.intercept.before()) {
return false;
}
}
if (this.interceptChain != null) {
//递归调用拦截器链中的拦截器的方法,如果只是调用doBefore(method),回陷入死循环
this.interceptChain.doBefore(method);
}
}
return true;
}
需要注意的是,一旦存在一个前置拦截为false,那么方法将不被执行,且后者拦截失效。
后置拦截器(冒泡执行):对于后置拦截器肯定是从内而外的执行,正好与前置拦截器相反,同样使用拦截器链为判断条件来递归调用,但是需要注意一个情况:当拦截器链为null时,拦截器不一定为null。具体实现如下:
//冒泡执行拦截器
public void doAfter(Method method) {
//找到最后一个拦截器
//可以嵌套的原因是当拦截器为null时,拦截器链必定为null
if (this.interceptChain != null) {
this.interceptChain.doAfter(method);
}
//到这里就是最后一个拦截器
if (intercept != null) {
if (Pattern.matches(cutPoint, method.toString())) {
this.intercept.after();
}
}
}
3、对代理对象执行方法的参数以及结果的获取与处理
对于代理实现的方法的参数与结果采用一个单例且线程安全的MethodArgAndResult来存储,
/**
* @author shkstart
* @create 2022-06-20 21:51
* 此类是一个单例的且线程安全的类,供用户获取代理执行的方法的参数与返回值
*/
public class MethodArgsAndResult {
private Object[] args;
private Object result;
//单例
private static MethodArgsAndResult methodArgsAndResult;
private MethodArgsAndResult() {
}
//线程安全的单例模式
public static MethodArgsAndResult getNewInstance() {
if (methodArgsAndResult == null) {
synchronized(MethodArgsAndResult.class) {
if (methodArgsAndResult == null) {
methodArgsAndResult = new MethodArgsAndResult();
}
}
}
return methodArgsAndResult;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
这样处理的好处在于,我们可以实现在自定义的拦截器中轻松获取到执行方法参数与返回值。
对于代理方法的执行我在MethodInvoker类中实现,在执行前赋值参数给单例的参数管理类,根据所有前置拦截器执行完毕后返回的结果决定是否继续执行。如下所示:
public Object methodInvoke() throws Exception {
//无拦截器直接执行并返回结果
if (this.interceptChain == null) {
return method.invoke(object, args);
}
//管理参数与结果
MethodArgsAndResult methodArgsAndResult = MethodArgsAndResult.getNewInstance();
methodArgsAndResult.setArgs(args);
methodArgsAndResult.setResult(null);
//前置拦截器返回值为false,不再执行此方法与后置拦截器
if (!this.interceptChain.doBefore(method)) {
return null;
}
System.out.println("执行中");
Object result = method.invoke(object, args);
methodArgsAndResult.setResult(result);
//执行后置拦截器
this.interceptChain.doAfter(method);
return result;
}
SpringAOP测试:
测试代码如下:
public class TestAOP {
public static void main(String[] args) {
LWProxy lwProxy = new LWProxy(LWProxy.JDK_PROXY);
lwProxy.addIntercept("MyInterface.*(*)", new Intercept() {
@Override
public boolean before() {
System.out.println("前置拦截A!");
return true;
}
@Override
public void after() {
System.out.println("后置拦截A!");
}
});
lwProxy.addIntercept("MyInterface.*(*)", new Intercept() {
@Override
public boolean before() {
System.out.println("前置拦截B!");
return true;
}
@Override
public void after() {
System.out.println("后置拦截B!");
}
});
SomeOne someOne = new SomeOne();
MyInterface myInterface = lwProxy.getProxy(someOne);
myInterface.doSome();
}
}
结果演示:
可以看到结果与设计的想法一致,当然这里只是简单的实现了SpringAOP中的核心功能,还需要进行更深入的学习相关知识。革命尚未成功,同志仍需努力!