Guice Aop 与 Hasor Aop 原理及其实现

余生长醉 提交于 2019-11-27 19:39:01

    这篇是承接《轻量级 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 的相关代码。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!