Wheel中两种AOP的实现

1.前言

在之前的文章中,简要的介绍了下wheel的设计思路,也专门写了篇文章给出了一个比较简单的实例,并且提供两种生成字节码的策略配置(运行时生成和部署前预生成)。那么一款MVC框架怎么能少了一个重要的成员呢,那就是AOP。本文就是介绍如何使用Wheel中方便快捷的实现AOP,同时又能够保持代码的优雅简洁

2.概述

这篇文章标题虽然是说两种AOP的实现,但是其底层都是通过修改字节码的方式将代码注入class中的各个位置的。那么区别在什么地方能,有两个:1.编写方式的不同。2.表现形式的不同。我们将这两种AOP实现方式称之为"ASMSupport方式的AOP"和"代码化的AOP",在介绍这两种方式之前,先来了解下Wheel中的两个概念:1.插入块, 2.插入点

3.插入块

在Wheel中插入块表示,我希望我的AOP程序是引用在哪个程序块上。分为三个级别:静态语句块,构造函数,普通函数。

4.插入点

插入点的概念其实和Spring AOP中的advice(通知)这个概念是一样的。在Wheel中我们定义了下面几种插入点:

  • before: 在调用方法之前
  • tryHead: 在调用原方法外添加一层try程序块,并且在try的最开始部分
  • tryFoot: 在调用原方法外添加一层try程序块,并且在try的最后的部分
  • inCatch: 在调用原方法的时候添加try...catch块,该插入点就在catch内部
  • inCatch: 在调用原方法的时候添加try...catch...finally或者try...finally块,该插入点就在finallys内部
  • after: 在调用方法之后
可能用程序更能够体现。代码如下:
public void test(){
    //before
    try{
        //tryHead
        调用原始代码
        //tryFoot
    }
    catch(Exception e){
        //inCatch
        throw e;
    }
    finally{
        //inFinally
    }
}

5.AOP的实现

在Wheel中我们采用继承的方式来实现各种需求的AOP,而对应的每一个插入块都有一个父类,在这些类中对每个插入点都有一个方法让子类实现。接下来就进入上文中讲到两种AOP的实现方式了。我们分别用这两种方式在三种插入块中实现打印功能,在每个插入点分别打印打入点的名称,比如在before点打印"before",那么接下来就是代码时间,我们所有的代码均在之前文章的实例基础上实现的。

5.1 ASMSupport方式的AOP

在这种方式的AOP中,插入块的父类分别是:cn.wensiqun.wheel.bean.aop.StaticBlockAdvice,cn.wensiqun.wheel.bean.aop.ConstructorAdvice, cn.wensiqun.wheel.bean.aop.MethodAdvice。他们分别对应的是class的静态程序块,构造函数和方法。 这种方式就是采用ASMSupport,自动生成我们希望的代码。OK,上代码:

5.1.1 静态程序块中

package jw.jwweb.mock.aop;

import cn.wensiqun.asmsupport.clazz.AClassFactory;
import cn.wensiqun.asmsupport.definition.value.Value;
import cn.wensiqun.asmsupport.definition.variable.GlobalVariable;
import cn.wensiqun.asmsupport.definition.variable.LocalVariable;
import cn.wensiqun.wheel.bean.aop.StaticBlockAdvice;

public class StaticTestAdvice extends StaticBlockAdvice {

	
	@Override
	public void before() {
		//拿到System.out
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
        //调用println方法,这里打印的是[Static Block][Static Block]before] I'm in + 修改的class名 + static block
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[Static Block][before]"));
	}
	

	@Override
	public void tryHead() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[Static Block][tryHead]"));
	}

	@Override
	public void tryFoot() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[Static Block][tryFoot]"));
	}

	@Override
	public void inCatch(LocalVariable exception) {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[Static Block][inCatch]"));
	}

	@Override
	public void inFinally() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[Static Block][inFinally]"));
	}

	@Override
	public void after() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[Static Block][after]"));
	}

	@Override
	public boolean condition(Class<?> owner) {
		//这里是判断条件,类似于spring中aop的表达式的功能,这不过这里我们将其代码化
		//为所有jw.jwweb.mock.service.impl包下的类加入这个AOP
		return owner.getName().startsWith("jw.jwweb.mock.service.impl");
	}

}

在上面的代码中我们要注意condition方法,这个方法就是判断传入的class是否允许做AOP。而其余的方法就是上文中所说的每个插入点对应的方法。要注意的一点就是在其父类中存在着几个常用方法:getBlock,getOwner, getCurrentAClass。分别是1.获取当前的ProgramBlock,这个ASMSupport修改class非常重要的类。2.获取当前做AOP所修改的类。3.获取当前做AOP所修改的AClass,这也是ASMSupport中非常重要的类。

5.1.2 构造函数

package jw.jwweb.mock.aop;

import java.lang.reflect.Constructor;

import cn.wensiqun.asmsupport.clazz.AClassFactory;
import cn.wensiqun.asmsupport.definition.value.Value;
import cn.wensiqun.asmsupport.definition.variable.GlobalVariable;
import cn.wensiqun.asmsupport.definition.variable.LocalVariable;
import cn.wensiqun.wheel.bean.aop.ConstructorAdvice;

public class ConstructorTestAdvice extends ConstructorAdvice {
	
	@Override
	public void before() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(this.getOwner().getName() + "[constructor][before]"));
	}
	

	@Override
	public void tryHead() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(this.getOwner().getName() + "[constructor][tryHead]"));
	}

	@Override
	public void tryFoot() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(this.getOwner().getName() + "[constructor][tryFoot]"));
	}

	@Override
	public void inCatch(LocalVariable exception) {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(this.getOwner().getName() + "[constructor][inCatch]"));
	}

	@Override
	public void inFinally() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(this.getOwner().getName() + "[constructor][inFinally]"));
	}

	@Override
	public void after() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(this.getOwner().getName() + "[constructor][after]"));
	}
	
	@Override
	protected boolean condition(Class<?> owner, Constructor<?> constructor) {
		//为所有jw.jwweb.mock.service.impl包下的所有类的所有构造方法加入这个AOP
		return owner.getName().startsWith("jw.jwweb.mock.service.impl");
	}

}

这里我们看到condition方法,多了一个参数constructor,由于我们现在是在构造方法上做AOP,所以我们可以做更多的过滤,比如当构造函数只有两个参数的时候我们才实现AOP。当然这里我们只是简单的判断下包名。这里面也有几个方法常用到的,除了getBlock,getOwner, getCurrentAClass之外,还有getArguments和getJoinPoint,第一个返回的是一个LocalVariable数组,这是ASMSupport中对变量的抽象,第二个返回的是一个java.lang.reflect.Constructor,表示当前所修改的构造函数。

5.1.3 方法

package jw.jwweb.mock.aop;

import java.lang.reflect.Method;

import cn.wensiqun.asmsupport.clazz.AClassFactory;
import cn.wensiqun.asmsupport.definition.value.Value;
import cn.wensiqun.asmsupport.definition.variable.GlobalVariable;
import cn.wensiqun.asmsupport.definition.variable.LocalVariable;
import cn.wensiqun.wheel.bean.aop.MethodAdvice;

public class MethodTestAdvice extends MethodAdvice {
	
	@Override
	public void before() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[" + getJoinPoint().getName() + "][before]"));
	}
	

	@Override
	public void tryHead() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[" + getJoinPoint().getName() + "][tryHead]"));
	}

	@Override
	public void tryFoot() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[" + getJoinPoint().getName() + "][tryFoot]"));
	}

	@Override
	public void inCatch(LocalVariable exception) {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[" + getJoinPoint().getName() + "][inCatch]"));
	}

	@Override
	public void inFinally() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[" + getJoinPoint().getName() + "][inFinally]"));
	}

	@Override
	public void after() {
		GlobalVariable out = AClassFactory.getProductClass(System.class).getGlobalVariable("out");
		getBlock().invoke(out, "println", Value.value(getOwner().getName() + "[" + getJoinPoint().getName() + "][after]"));
	}
	
	@Override
	protected boolean condition(Class<?> owner, Method method) {
		//为所有jw.jwweb.mock.service.impl包下的getUsers方法加入这个AOP
		return owner.getName().startsWith("jw.jwweb.mock.service.impl") && method.getName().equals("getUsers");
	}

}

这个就和构造方法的AOP类似了,不同的有两点:构造方法condition的参数是Constructor,而这里是java.lang.reflet.Method。同样getJoinPoint返回的是当前修改的方法,也是java.lang.reflet.Method类型

至此第一种方式的AOP就介绍完了,可能看到这里会觉得Wheel的AOP非常难,并且不容易上手,的确,ASMSupport也是鄙人开发的,没有什么普及度可言。虽然Wheel底层框架就是用ASMSupport实现的,但是对于框架的使用者来说完全没必要花时间再去学习ASMSupport,那么为了解决这个问题,Wheel提供了第二种方式。

5.2 代码化的AOP

通过前面的方式我们了解到,asmsupport的aop解决方法,但是我们必定是希望aop越简单越好,就像我们平时开发java业务逻辑一样的,为此我们编写了这种方式的AOP。我们是需要使用java代码实现业务逻辑即可,别管TMD什么字节码asmsupport。所以我们称之为代码化的AOP,虽然不同于之前的AOP实现方式,但是整体框架还是没有变的,依然是三个插入块,六个插入点。那么对应于三个插入块的抽象类是:CodeOfStaticBlockAdvice, CodeOfConstructorAdvice, CodeOfMethodAdvice。同样我们实现上面的功能

5.2.1 静态程序块中

package jw.jwweb.mock.aop;

import cn.wensiqun.wheel.bean.aop.CodeOfStaticBlockAdvice;

public class CodeOfStaticTestAdvice extends CodeOfStaticBlockAdvice {
	
	@Override
	public void before() {
		System.out.println(getOwner().getName() + "[Static Block][code of before]");
	}

	@Override
	public void tryHead() {
		System.out.println(getOwner().getName() + "[Static Block][code of tryHead]");
	}

	@Override
	public void tryFoot() {
		System.out.println(getOwner().getName() + "[Static Block][code of tryFoot]");
	}

	@Override
	public void inCatch(Throwable e) {
		System.out.println(getOwner().getName() + "[Static Block][code of inCatch]");
	}

	@Override
	public void inFinally() {
		System.out.println(getOwner().getName() + "[Static Block][code of inFinally]");
	}

	@Override
	public void after() {
		System.out.println(getOwner().getName() + "[Static Block][code of after]");
	}

	@Override
	public boolean condition(Class<?> owner) {
		return owner.getName().startsWith("jw.jwweb.mock.service.impl");
	}

}

这里和上面一样,有一个getOwner()方法,获取当前修改的Class

5.2.2构造函数

package jw.jwweb.mock.aop;

import java.lang.reflect.Constructor;

import cn.wensiqun.wheel.bean.aop.CodeOfConstructorAdvice;

public class CodeOfConstructorTestAdvice extends CodeOfConstructorAdvice {
	
	@Override
	public void before() {
		System.out.println(getOwner().getName() + "[constructor][code of before]");
	}

	@Override
	public void tryHead() {
		System.out.println(getOwner().getName() + "[constructor][code of tryHead]");
	}

	@Override
	public void tryFoot() {
		System.out.println(getOwner().getName() + "[constructor][code of tryFoot]");
	}

	@Override
	public void inCatch(Throwable e) {
		System.out.println(getOwner().getName() + "[constructor][code of inCatch]");
	}

	@Override
	public void inFinally() {
		System.out.println(getOwner().getName() + "[constructor][code of inFinally]");
	}

	@Override
	public void after() {
		System.out.println(getOwner().getName() + "[constructor][code of after]");
	}

	@Override
	public boolean condition(Class<?> owner, Constructor<?> construcotr) {
		return owner.getName().startsWith("jw.jwweb.mock.service.impl");
	}

}

在CodeOfConstructorAdvice 中有三个常用方法getOwner,getJoinPoint,getArguments方法,基本和ASMSupport方式的AOP中的方法是一样的。只不过这里getArguments返回的类型就是Object[]类型,如果当前所修改的构造函数是没有参数的,那么Object[]就是一个非null的空数组。getJoinPoint照例是返回的当前修改的Constructor.

5.2.3 方法

package jw.jwweb.mock.aop;

import java.lang.reflect.Method;

import cn.wensiqun.wheel.bean.aop.CodeOfMethodAdvice;

public class CodeOfMethodTestAdvice extends CodeOfMethodAdvice {
	

	@Override
	public void before() {
		System.out.println(getOwner().getName() + "[" + getJoinPoint().getName() + "][code of before]");
	}

	@Override
	public void tryHead() {
		System.out.println(getOwner().getName() + "[" + getJoinPoint().getName() + "][code of tryHead]");
	}

	@Override
	public void tryFoot() {
		System.out.println(getOwner().getName() + "[" + getJoinPoint().getName() + "][code of tryFoot]");
	}

	@Override
	public void inCatch(Throwable e) {
		System.out.println(getOwner().getName() + "[" + getJoinPoint().getName() + "][code of inCatch]");
	}

	@Override
	public void inFinally() {
		System.out.println(getOwner().getName() + "[" + getJoinPoint().getName() + "][code of inFinally]");
	}

	@Override
	public void after() {
		System.out.println(getOwner().getName() + "[" + getJoinPoint().getName() + "][code of after]");
	}

	
	@Override
	public boolean condition(Class<?> owner, Method method) {
		//为所有jw.jwweb.mock.service.impl包下的getUsers方法加入这个AOP
		return owner.getName().startsWith("jw.jwweb.mock.service.impl") && method.getName().equals("getUsers");
	}

}

在CodeOfConstructorAdvice中也有三个常用方法getOwner,getJoinPoint,getArguments方法,和上面介绍的一样,只不过getJoinPoint返回的是当前修改的方法,类型是java.lang.reflet.Method.

从上面的代码可以看出,这种方式就是纯业务逻辑了。至于condition方法的实现则还是一样的。所以这种方式就简单的多了。至于这两种方式有什么不同的。接下来我们就要看看生成的构造方法字节码内容了。我们通过反编译软件看看生成的class是什么,我们只截取一部分来看。

public MockServiceImpl()
	    throws Throwable
	  {
		//代码化的方式的AOP
	    Constructor localConstructor = null;
	    try
	    {
	      localConstructor = getClass().getDeclaredConstructor(new Class[0]);
	    }
	    catch (Throwable e)
	    {
	      throw new WheelRuntimeException(e);
	    }
	    Object[] e1 = new Object[0];
	    CodeOfConstructorTestAdvice localCodeOfConstructorTestAdvice = new CodeOfConstructorTestAdvice();
	    localCodeOfConstructorTestAdvice.setConstructor(localConstructor);
	    localCodeOfConstructorTestAdvice.setArguments(e1);
	    localCodeOfConstructorTestAdvice.init();
	    localCodeOfConstructorTestAdvice.before();
	    
	    //ASMSupport方式的AOP
	    System.out.println("jw.jwweb.mock.service.impl.MockServiceImpl[constructor][before]");
	    try
	    {
	      //代码化的方式的AOP
	      localCodeOfConstructorTestAdvice.tryHead();
	      //ASMSupport方式的AOP
	      System.out.println("jw.jwweb.mock.service.impl.MockServiceImpl[constructor][tryHead]");
	      //这里调用原始内容
	      ...
	    }
	    catch (Throwable e)
	    {
	      ...
	    }
	    finally
	    {
	      ...
	    }
	    ...
	  }

上面注解中我们可以看到,哪些是ASMSupport方式生成的,哪些是代码化方式生成的。下面就是这两种的区别

  • ASMSupport方式的AOP在字节码层面更趋近于真实方法调用;而代码化的方式则刷了个小手段,生成一个对象,在对象内实现我们的代码,而这样也导致了如果aop对象比较多,那么在每次进入这个方法的时候都将对每个AOP创建出对应的实例,但是开发难度上明显优于前者。

在代码化方式的AOP中如何在同一个AOP中共享全局变量,很简单,参考如下代码:

public class SharedVariableMethod extends CodeOfMethodAdvice {
	
	private String previousAdvice;

	@Override
	public void before() {
		previousAdvice = "before";
	}

	@Override
	public void tryHead() {
		System.out.println(previousAdvice);
		previousAdvice = "tryHead";
	}

	@Override
	public void tryFoot() {
		System.out.println(previousAdvice);
		previousAdvice = "tryFoot";
	}

	@Override
	public void inCatch(Throwable e) {
		System.out.println(previousAdvice);
		previousAdvice = "inCatch";
	}

	@Override
	public void inFinally() {
		System.out.println(previousAdvice);
		previousAdvice = "inFinally";
	}

	@Override
	public void after() {
		System.out.println(previousAdvice);
		previousAdvice = "after";
	}

	
	@Override
	public boolean condition(Class<?> owner, Method method) {
		return owner.getName().startsWith("jw.jwweb.mock.service.impl") && method.getName().equals("getUsers");
	}

}

那么有一点需要注意的就是,就是变量访问的顺序,我不能在inFinally里面将变量设置成"test"然后期望在before中将其打印处理。

6 结果

在浏览器发送请求http://host/WheelSampleApp/MockServlet/process.action?type=FORWARD。在控制台看到如下内容

jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][code of before]
jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][before]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][code of before]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][before]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][code of tryHead]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][tryHead]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][code of tryFoot]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][tryFoot]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][code of inFinally]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][inFinally]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][code of after]
jw.jwweb.mock.service.impl.MockServiceImpl[constructor][after]
jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][code of tryHead]
jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][tryHead]
jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][code of tryFoot]
jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][tryFoot]
jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][code of inFinally]
jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][inFinally]
jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][code of after]
jw.jwweb.mock.service.impl.MockServiceImpl[Static Block][after]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][code of before]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][before]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][code of tryHead]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][tryHead]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][code of tryFoot]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][tryFoot]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][code of inFinally]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][inFinally]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][code of after]
jw.jwweb.mock.service.impl.MockServiceImpl[getUsers][after]

是不是很乱,理理就清楚了。这里有一点可能大家要疑惑就是第三行,为什么静态语句块还没执行完就调用了构造方法,这是因为MockServiceImpl采用@Singleton注解,这样在静态程序块中就初始化了一个实例。

PS:还有个很重要的忘记说了,所有的AOP实现类,都必须采用无参的构造方法。

你可能感兴趣的:(字节码,AOP,mvc,ASMSupport,Wheel)