这篇是承接《轻量级 Java 开发框架 设计》系列Blog文的后续文章,本文主要介绍 Hasor 中 AOP 方面的设计以及实现。
提起 Aop 大家并不陌生,OSC中也不缺乏优秀的介绍文章。因而我也不费唇舌去介绍什么是 AOP 以及AOP 的切面概念。下面就转入正题。
Hasor 的核心采用的是 Google Guice 也就是说,AOP 核心实现也就是 Guice 提供的。因此本文首先要介绍 Guice 是如何实现 Aop 然后在介绍 Hasor 的 Aop 实现方式。所以什么 CGLib、ASM、Javassist 都先闪到一遍去把,没必要搞一个重复的功能添加进来。
本文将分为三个部分讲解:
1 Guice Aop:介绍使用原生 Guice 方法实现 Aop。
2 Hasor Aop:介绍使用 Hasor 的 Aop 如何使用。
3 讲解 Hasor 是如何实现 Aop 以及如何处理 Aop 链问题的。
Guice Aop
先看看如何使用原生 Guice 实现 AOP 把,下面是 Guice 中一个标准的切面完整代码:
class MyInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("before."); Object returnData = invocation.proceed(); System.out.println("after."); return returnData; } }
创建 Guice 使用下面这个代码:
class MyModule implements Module { public void configure(Binder binder) { // TODO Auto-generated method stub } } public static void main(String[] args) { Guice.createInjector(new MyModule()); }
下面我们将介绍如何确定哪些类需要 Aop。首先 Guice 是通过两个匹配器完成 Aop 是否加装的判断。 它们一个是负责匹配类、一个是负责匹配类中的方法。
使用 Guice 做 Aop 有一个好处就是,你永远不需要预先准备哪些类需要 Aop。当你通过 Guice 创建类对象时 Guice 会通过匹配器来判断创建的类型是否满足加装 Aop,以及加装哪些 Aop。
接下来我们会关心匹配器的问题,下面这段代码中 “Matchers.inSubpackage("org.more.test")” 表示匹配“org.more.test.guice” 包下的所有类。而 “Matchers.any()” 部分的含义是匹配所有方法。
public class AopTest { static class MyInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("before."); Object returnData = invocation.proceed(); System.out.println("after."); return returnData; } } static class MyModule implements Module { public void configure(Binder binder) { Matcher<Class> m = Matchers.inSubpackage("org.more.test"); binder.bindInterceptor(m, Matchers.any(), new MyInterceptor()); } } // public void foo() { System.out.println("Hello Word."); } // public static void main(String[] args) { Injector injector = Guice.createInjector(new MyModule()); AopTest obj = injector.getInstance(AopTest.class); obj.foo(); } }
如果你对“Matchers.inSubpackage("org.more.test")”这种方式感到不满,可以通过实现 Matcher 接口制定适合自己的筛选器。Hasor 就是通过制定筛选器达到目的的。
好了上面对 Guice 本身如何实现 Aop 做了一个简短的介绍,接下来将介绍 Hasor 中如何实现 Aop 的。
Hasor Aop
从理论上来说 Aop 被分为三个切面(调用前、调用后、异常发生)。同时当配置多个 Aop 时还会涉及到 “链” 的问题。而这就像是同心圆环,最内层的是目标方法。要想调用到目标方法需要逐一经过其外面的 Aop切面程序。
在实现 Aop 时候有两条路可以选择:
一、是制定一个类似 Filter 的接口 Api 使调用链式结构的 Aop代理变得像是在操作过滤器。
二、是声明定义三个接口专门用于通知切面程序三个切点事件,在拦截器中切点位置执行切面方法调用。
前一种方式可能会面临设计结构的问题,这种方式可以在“Aop过滤器”链中控制是否要继续向下执行。而后一种虽然结构非常清晰,但是切面程序也失去了对 Aop 链控制。
从开发角度来看 “过滤器” 也可以得到(调用前、调用后、异常发生)这三个事件点。因此 Hasor 放弃了第二种实现方式。那么先看一下使用 Hasor Api 如何开发 Aop 程序:
public class SimpleAop_Test { public static class MyInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("before."); Object returnData = invocation.proceed(); System.out.println("after."); return returnData; } } // @Aop(MyInterceptor.class) public void foo() { System.out.println("Hello Word."); } // public static void main(String[] args) throws IOException { AppContext appContext = new AnnoStandardAppContext(); appContext.start(); // SimpleAop_Test obj = appContext.getInstance(SimpleAop_Test.class); obj.foo(); } }
从代码比较上来看,除了使用 @Aop 注解并没有什么太大的改进。不过在这里我要先给大家说明一下,Guice 是一款 IoC/Aop 框架。切面程序按理来说一旦被容器管理也应当可以被依赖注入。不过您可以留心观察一下,在 Guice 的原生实现方式中拦截器是被 new 出来的,通过查看 Guice API也不难发现注册拦截器也只有这一种方式。
使用 Hasor 的 Aop 除了简单的通过 @Aop 注解就可以实现之外,拦截器类还可以被依赖注入。这样一来 Guice 在 Aop 开发方面就变得更加友好了。
@Aop 注解是可以被标记到(方法 或 类)上,通过标记位置的不同决定了 Aop 作用范围。当然您可以混合使用例如:
@Aop(MyInterceptor.class) public class SimpleAop_Test { public void foo1() { System.out.println("Hello Word."); } @Aop(MyInterceptor2.class) public void foo2() { System.out.println("Hello Word."); } }
或许有的时候还需要全局 Aop 配置,@Aop 并不支持全局配置。因此需要借助 Hasor 的插件来实现这一目标:
@Plugin public class GlobalAopInterceptor implements HasorPlugin { public void loadPlugin(ApiBinder apiBinder) { Matcher matcher = ......; apiBinder.getGuiceBinder().bindInterceptor(// matcher, Matchers.any(), new MyInterceptor()); } }
Hasor Aop 实现源码分析
那么 Hasor 是如何实现这一功能的呢?
在 Hasor 中 @Aop 并不是 Hasor 核心直接提供的功能。它是由一个 Hasor 插件提供的,这个插件仅有几个类组成。它们位于:“net.hasor.plugins.aop” 包下。
接下来让我们我们先看看插件入口程序:
@Plugin public class AopPlugin extends AbstractHasorPlugin { public void loadPlugin(ApiBinder apiBinder) { Matcher<Object> matcher = AopMatchers.annotatedWith(Aop.class);// apiBinder.getGuiceBinder().bindInterceptor(matcher, matcher, new AopInterceptor()); } }
作为 Aop 实现的入口我们看到,Aop 插件仅仅是向 Guice 注册了一个拦截器。这个拦截器负责拦截所有标记了 @Aop 注解的类或方法。下面是相关匹配器的代码:
/** * 负责检测匹配。规则:只要类型或方法上标记了某个注解。 * @version : 2013-11-22 * @author 赵永春(zyc@hasor.net) */ class MatcherAnnotationType extends AbstractMatcher<Object> { private Class<? extends Annotation> annotationType = null; public MatcherAnnotationType(Class<? extends Annotation> annotationType) { this.annotationType = annotationType; } public boolean matches(Object type) { if (type instanceof Class<?>) return this.matches((Class<?>) type); if (type instanceof Method) return this.matches((Method) type); return false; } public boolean matches(Class<?> matcherType) { if (matcherType.isAnnotationPresent(this.annotationType) == true) return true; Method[] m1s = matcherType.getMethods(); Method[] m2s = matcherType.getDeclaredMethods(); for (Method m1 : m1s) { if (m1.isAnnotationPresent(this.annotationType) == true) return true; } for (Method m2 : m2s) { if (m2.isAnnotationPresent(this.annotationType) == true) return true; } return false; } public boolean matches(Method matcherType) { if (matcherType.isAnnotationPresent(this.annotationType) == true) return true; if (matcherType.getDeclaringClass().isAnnotationPresent(this.annotationType) == true) return true; return false; } }
有了这个匹配器,只要调用带有 @Aop 标记的类或方法。都会进入我们的拦截器 AopInterceptor,下面是拦截器代码(为了缩短代码长度,下面的代码中去掉了部分泛型声明):
class AopInterceptor implements MethodInterceptor, AppContextAware { private AppContext appContext = null; private Map methodInterceptorMap = new HashMap(); // public AopInterceptor() { AwareUtil.registerAppContextAware(this); } // public void setAppContext(AppContext appContext) { this.appContext = appContext; } // public Object invoke(MethodInvocation invocation) throws Throwable { Method targetMethod = invocation.getMethod(); List<Class> list = this.methodInterceptorMap.get(targetMethod); //1.取得拦截器 if (list == null) { list = new ArrayList(); Aop beforeAnno = targetMethod.getDeclaringClass().getAnnotation(Aop.class); if (beforeAnno != null) { for (Class interType : beforeAnno.value()) if (interType != null) list.add(interType); } beforeAnno = targetMethod.getAnnotation(Aop.class); if (beforeAnno != null) { for (Class interType : beforeAnno.value()) if (interType != null) list.add(interType); } this.methodInterceptorMap.put(targetMethod, list); } //2.创建对象 return new AopChainInvocation(appContext, list, invocation).invoke(invocation); } }
上面这段代码中,有关“AppContextAware”部分的内容稍后介绍。首先我们假设 AppContext 已经存在。当拦截器拦截到符合 @Aop 的方法调用之后。这个拦截器会取得调用方法的 Method 对象。
接下来拦截器会尝试从 Method 对象中获取 @Aop 注解中配置的拦截器信息。
当然,这里考虑到了拦截器链的问题,因此会有一个 List 对象用于收集这个方法调用都配置了哪些真正的Aop 拦截器。
最后利用收集到的信息构造一个“AopChainInvocation” 对象来处理调用过滤器链,下面是源码:
class AopChainInvocation implements MethodInvocation { private MethodInterceptor[] beforeInterceptor = null; private MethodInvocation invocation = null; private int index = -1; // public AopChainInvocation(AppContext appContext, List<Class> interTypeList, MethodInvocation invocation) { List<MethodInterceptor> beforeList = new ArrayList<MethodInterceptor>(); for (Class<? extends MethodInterceptor> interType : interTypeList) { if (interType != null) beforeList.add(appContext.getInstance(interType)); } this.beforeInterceptor = beforeList.toArray(new MethodInterceptor[beforeList.size()]); this.invocation = invocation; } public Object invoke(MethodInvocation invocation) throws Throwable { index++; if (index < beforeInterceptor.length) { return beforeInterceptor[index].invoke(this); } else { return invocation.proceed(); } } //----------------------------------------------------------- public Object[] getArguments() { return invocation.getArguments(); } public Object proceed() throws Throwable { return this.invoke(this.invocation); } public Object getThis() { return invocation.getThis(); } public AccessibleObject getStaticPart() { return invocation.getStaticPart(); } public Method getMethod() { return invocation.getMethod(); } }
AppContextAware接口是由“net.hasor.plugins.aware”插件提供的。这个插件功能是给予那些不方便获通过注入方式获取 AppContext 接口对象的类。在 AppContext 启动的第一时间给予它们注入。
以上就是 Hasor 中有关 Aop 方面的完整说明。
----------------------------------------------------------------
目前的开发代码存放于(包括Demo程序):
Github: https://github.com/zycgit/hasor
git@OSC: http://git.oschina.net/zycgit/hasor
非常感谢您百忙之中抽出时间来看这一系博文。可以通过Maven 中央仓库网站 http://search.maven.org/ 搜索 Hasor 下载 hasor 的相关代码。