JFinal 源码解析-AOP部分

一直比较欣赏jfinal aop对于aop的责任链模式轻量实现,对其实现原理比较好奇,趁着最近手头工作告一段落便来阅读一下jfinal源码,作者波总关于aop的解读镇楼。

image

先来看看jfinal aop的使用方法,我们对一个Service进行增强的代码如下:

NoticeService service = Duang.duang(NoticeService.class);

接下来顺藤摸瓜,沿着这条线索走下去看看jfinal做了什么:

首先Duang这个类只是一个空壳子,依赖Enhancer类进而使用cglib来实现增强

    public static <T> T duang(Class<T> targetClass) {
        return (T)Enhancer.enhance(targetClass);
    }
    public static <T> T enhance(Class<T> targetClass) {
        return (T)net.sf.cglib.proxy.Enhancer.create(targetClass, new Callback());
    }

此处可以看到Callback是cglib的拦截器实现,用于对被增强类进行代理,我们可以在自定义的MethodInterceptor实现类的intercept方法里,对调用业务代码前后做一些事情

/**
 * Callback.
 */
class Callback implements MethodInterceptor {

核心代码intercept:

    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if (excludedMethodName.contains(method.getName())) {
            // if (method.getName().equals("finalize"))
            //  return methodProxy.invokeSuper(target, args);
            // return this.injectTarget != null ? methodProxy.invoke(this.injectTarget, args) : methodProxy.invokeSuper(target, args);
            
            // 保留上面注释部分,此处为优化
            if (this.injectTarget == null || method.getName().equals("finalize")) {
                return methodProxy.invokeSuper(target, args);
            } else {
                return methodProxy.invoke(this.injectTarget, args);
            }
        }
        
        if (this.injectTarget != null) {
            target = this.injectTarget;
            Interceptor[] finalInters = interMan.buildServiceMethodInterceptor(injectInters, target.getClass(), method);
            Invocation invocation = new Invocation(target, method, args, methodProxy, finalInters);
            invocation.useInjectTarget = true;
            invocation.invoke();
            return invocation.getReturnValue();
        }
        else {
            Class<?> targetClass = target.getClass();
            if (targetClass.getName().indexOf("$$EnhancerByCGLIB") != -1) {
                targetClass = targetClass.getSuperclass();
            }
            Interceptor[] finalInters = interMan.buildServiceMethodInterceptor(injectInters, targetClass, method);
            Invocation invocation = new Invocation(target, method, args, methodProxy, finalInters);
            invocation.useInjectTarget = false;
            invocation.invoke();
            return invocation.getReturnValue();
        }
    }

这个intercept方法其实就做了一件事情:组装Invocation对象并调用invoke方法返回执行结果。

            Interceptor[] finalInters = interMan.buildServiceMethodInterceptor(injectInters, targetClass, method);
            Invocation invocation = new Invocation(target, method, args, methodProxy, finalInters);
            invocation.useInjectTarget = false;
            invocation.invoke();
            return invocation.getReturnValue();

通常我们使用cglib实现动态代理时,需要实现的before和after操作都是在这个interceptor里面直接或间接实现,例如下面这段逻辑:


image.png

而jfinal为了实现aop操作面向业务开发人员的可配置和可扩展,将这部分放到业务人员编写的jfinal Interceptor接口实现类实现,在Invocation.invoke方法里去递归执行业务代码编写人员写好的所有拦截器,当递归结束后,最后执行被代理的业务方法。

Invocation.invoke方法源码:

    public void invoke() {
        if (index < inters.length) {
            inters[index++].intercept(this);
        }
        else if (index++ == inters.length) {    // index++ ensure invoke action only one time
            try {
                // Invoke the action
                if (action != null) {
                    returnValue = action.getMethod().invoke(target, args);
                }
                // Invoke the method
                else {
                    // if (!Modifier.isAbstract(method.getModifiers()))
                        // returnValue = methodProxy.invokeSuper(target, args);
                    if (useInjectTarget)
                        returnValue = methodProxy.invoke(target, args);
                    else
                        returnValue = methodProxy.invokeSuper(target, args);
                }
            }
            catch (InvocationTargetException e) {
                Throwable t = e.getTargetException();
                throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(e);
            }
            catch (RuntimeException e) {
                throw e;
            }
            catch (Throwable t) {
                throw new RuntimeException(t);
            }
        }
    }

此处可以看到有这样一段逻辑控制代码:

    public void invoke() {
        if (index < inters.length) {
            inters[index++].intercept(this);
        }
        else if (index++ == inters.length) {
          调用被代理业务方法......
        }

和我们编写的业务Interceptor中的一段代码遥相呼应,下图是框架使用人员编写的一个业务拦截器:

public class APIExceptionInterceptor implements Interceptor
{
    
    private static Logger logger = LoggerFactory.getLogger(APIExceptionInterceptor.class);

    @Override
    public void intercept(Invocation inv)
    {
        Controller controller = inv.getController();
        try {
            inv.invoke();
        } catch (Exception e) {
            logger.error("================={}.{}接口调用异常,参数{},异常信息:", controller.getClass().getName(), inv.getMethodName(), JsonKit.toJson(controller.getParaMap()), e);
            controller.renderJson(ResponseMapUtil.getFailedResponseMap(ErrorConstants.SERVER_API_ERR));
        }
    }

}

我们重点关注这行代码:

inv.invoke();

当执行第一个拦截器时,会调用invoke,然后继续回到Invocation.invoke函数的执行过程:

    public void invoke() {
        if (index < inters.length) {
            inters[index++].intercept(this);
        }
        else if (index++ == inters.length) {
          调用被代理业务方法......
        }

此时的index已经递增到了1,所以又继续执行第二个拦截器,就这样互相回调,形成了一个递归,直到执行完所有的拦截器,递归才会结束。
执行完所有拦截器后,开始调用被代理的业务方法,拿到执行结果并返回,就执行完了一次aop操作,以下是Invocation.invoke中【调用被代理业务方法...】的源码:

            try {
                // Invoke the action
                if (action != null) {
                    returnValue = action.getMethod().invoke(target, args);
                }
                // Invoke the method
                else {
                    // if (!Modifier.isAbstract(method.getModifiers()))
                        // returnValue = methodProxy.invokeSuper(target, args);
                    if (useInjectTarget)
                        returnValue = methodProxy.invoke(target, args);
                    else
                        returnValue = methodProxy.invokeSuper(target, args);
                }
            }
            catch (InvocationTargetException e) {
                Throwable t = e.getTargetException();
                throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(e);
            }
            catch (RuntimeException e) {
                throw e;
            }
            catch (Throwable t) {
                throw new RuntimeException(t);
            }

声明式事务、日志处理等操作都能够支持,后面的拦截器执行过程将会被前面执行的拦截器所包裹,类似于装饰器模式的装饰。
JFinal只是用了一系列拦截器加上一个递归调用操作,就实现了极简aop,很厉害。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容