在之前的文章中,简要的介绍了下wheel的设计思路,也专门写了篇文章给出了一个比较简单的实例,并且提供两种生成字节码的策略配置(运行时生成和部署前预生成)。那么一款MVC框架怎么能少了一个重要的成员呢,那就是AOP。本文就是介绍如何使用Wheel中方便快捷的实现AOP,同时又能够保持代码的优雅简洁
这篇文章标题虽然是说两种AOP的实现,但是其底层都是通过修改字节码的方式将代码注入class中的各个位置的。那么区别在什么地方能,有两个:1.编写方式的不同。2.表现形式的不同。我们将这两种AOP实现方式称之为"ASMSupport方式的AOP"和"代码化的AOP",在介绍这两种方式之前,先来了解下Wheel中的两个概念:1.插入块, 2.插入点
在Wheel中插入块表示,我希望我的AOP程序是引用在哪个程序块上。分为三个级别:静态语句块,构造函数,普通函数。
插入点的概念其实和Spring AOP中的advice(通知)这个概念是一样的。在Wheel中我们定义了下面几种插入点:
public void test(){ //before try{ //tryHead 调用原始代码 //tryFoot } catch(Exception e){ //inCatch throw e; } finally{ //inFinally } }
在Wheel中我们采用继承的方式来实现各种需求的AOP,而对应的每一个插入块都有一个父类,在这些类中对每个插入点都有一个方法让子类实现。接下来就进入上文中讲到两种AOP的实现方式了。我们分别用这两种方式在三种插入块中实现打印功能,在每个插入点分别打印打入点的名称,比如在before点打印"before",那么接下来就是代码时间,我们所有的代码均在之前文章的实例基础上实现的。
在这种方式的AOP中,插入块的父类分别是:cn.wensiqun.wheel.bean.aop.StaticBlockAdvice,cn.wensiqun.wheel.bean.aop.ConstructorAdvice, cn.wensiqun.wheel.bean.aop.MethodAdvice。他们分别对应的是class的静态程序块,构造函数和方法。 这种方式就是采用ASMSupport,自动生成我们希望的代码。OK,上代码:
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中非常重要的类。
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,表示当前所修改的构造函数。
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提供了第二种方式。
通过前面的方式我们了解到,asmsupport的aop解决方法,但是我们必定是希望aop越简单越好,就像我们平时开发java业务逻辑一样的,为此我们编写了这种方式的AOP。我们是需要使用java代码实现业务逻辑即可,别管TMD什么字节码asmsupport。所以我们称之为代码化的AOP,虽然不同于之前的AOP实现方式,但是整体框架还是没有变的,依然是三个插入块,六个插入点。那么对应于三个插入块的抽象类是:CodeOfStaticBlockAdvice, CodeOfConstructorAdvice, CodeOfMethodAdvice。同样我们实现上面的功能
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
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.
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方式生成的,哪些是代码化方式生成的。下面就是这两种的区别
在代码化方式的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中将其打印处理。
在浏览器发送请求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实现类,都必须采用无参的构造方法。