当RxJava遇到AOP

如来神掌.jpg

背景

公司打算开发一款全新的To C产品,因此我开始做一些搭建框架的事儿以及POC。新的产品能够使用一些比较新的技术,在新产品中我大量使用了Rx。这就导致了原先的AOP框架在某些场景下是无法使用的,借此机会我顺便升级了一下原先的AOP框架。

回顾一下之前写过的一篇文章归纳AOP在Android开发中的几种常见用法
AOP框架地址:https://github.com/fengzhizi715/SAF-AOP

@Trace

在之前的文章中讲过@Trace,它能追踪某个方法花费的时间。如果想这样追踪匿名内部类花费的时间,原先的代码是无法使用的,只能追踪到initData()花费的时间。

    @Trace
    private void initData() {

        Observable.create(new ObservableOnSubscribe<String>() {

            @Trace
            @Override
            public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {

                e.onNext("111");
                e.onNext("222");
                e.onNext("333");

            }
        }).subscribe(new Consumer<String>() {

            @Trace
            @Override
            public void accept(@NonNull String str) throws Exception {

            }
        });
    }

改了一下TraceAspect

package com.safframework.aop;

import com.safframework.aop.annotation.Trace;
import com.safframework.log.L;
import com.safframework.tony.common.utils.Preconditions;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

/**
 * Created by Tony Shen on 16/3/22.
 */
@Aspect
public class TraceAspect {

    private static final String POINTCUT_METHOD = "execution(@com.safframework.aop.annotation.Trace * *(..))";

    private static final String POINTCUT_CONSTRUCTOR = "execution(@com.safframework.aop.annotation.Trace *.new(..))";

    @Pointcut(POINTCUT_METHOD)
    public void methodAnnotatedWithTrace() {
    }

    @Pointcut(POINTCUT_CONSTRUCTOR)
    public void constructorAnnotatedTrace() {
    }

    @Around("methodAnnotatedWithTrace() || constructorAnnotatedTrace()")
    public Object traceMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        Trace trace = methodSignature.getMethod().getAnnotation(Trace.class);
        if (!trace.enable()) {
            return joinPoint.proceed();
        }

        String className = methodSignature.getDeclaringType().getSimpleName();
        String methodName = methodSignature.getName();
        final StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object result = joinPoint.proceed();
        stopWatch.stop();

        if (Preconditions.isBlank(className)) {
            className = "Anonymous class";
        }

        L.i(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));

        return result;
    }

    /**
     * Create a log message.
     *
     * @param methodName A string with the method name.
     * @param methodDuration Duration of the method in milliseconds.
     * @return A string representing message.
     */
    private static String buildLogMessage(String methodName, long methodDuration) {
        StringBuilder message = new StringBuilder();
        message.append(methodName);
        message.append("()");
        message.append(" take ");
        message.append("[");
        message.append(methodDuration);
        message.append("ms");
        message.append("]");

        return message.toString();
    }
}

在这里,
@Pointcut 表示拦截的切入点方法,是方法级别之上的注解,但是不执行方法体,只表示切入点的入口。

@Around 用于判断是否执行以上的拦截。

这样,原先的代码就能work了,在创建Observable时发射了三次,所以在subscribe时也接收到三次accept(),符合预期。


@Trace的追踪效果.jpeg

@HookMethod

@HookMethod在之前的文章中也讲到过,是比较经典的AOP使用方式,能在方法执行前后进行hook。

我同样也修改了HookMethodAspect,使得@HookMethod也能够在匿名内部类中使用,满足相关的业务场景。

RxView.clicks(holder.imageView)
                .compose(RxJavaUtils.preventDuplicateClicksTransformer())
                .subscribe(new Consumer<Object>() {

                    @HookMethod(beforeMethod = "saveContentToDB")
                    @Override
                    public void accept(@NonNull Object o) throws Exception {

                        Intent intent = new Intent(mContext, ContentDetailActivity.class);
                        intent.putExtra("content_item",item);
                        intent.putExtra("page_start_time",new DateTime());
                        mContext.startActivity(intent);
                    }

                    private void saveContentToDB() {

                        if (User.currentUser().isLoggedIn()){

                            App.getInstance().queue.addOperation(new Operation() {
                                @Override
                                public void run(Queue queue, Bundle bundle) {

                                    DBUtils.insertContent(item);
                                }
                            });
                        }
                    }
                });

需要注意的是,目前beforeMethod、afterMethod所对应的方法只能位于所在类中。可能,未来会针对这一块做一些优化。

@CheckLogin

由于跟业务结合紧密,@CheckLogin并不在框架中。但是在项目中使用它完全没问题。

首先,定义注解

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;

/**
 * Created by Tony Shen on 2017/7/26.
 */

@Target({METHOD, CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface CheckLogin {
}

再定义一个Aspect

/**
 * Created by Tony Shen on 2017/7/26.
 */

@Aspect
public class CheckLoginAspect {

    private static final String POINTCUT_METHOD = "execution(@cn.magicwindow.toutiao.aop.CheckLogin * *(..))";

    private static final String POINTCUT_CONSTRUCTOR = "execution(@cn.magicwindow.toutiao.aop.CheckLogin *.new(..))";

    @Pointcut(POINTCUT_METHOD)
    public void methodAnnotatedWithCheckLogin() {
    }

    @Pointcut(POINTCUT_CONSTRUCTOR)
    public void constructorAnnotatedCheckLogin() {
    }

    @Around("methodAnnotatedWithCheckLogin() || constructorAnnotatedCheckLogin()")
    public void checkLogin(final ProceedingJoinPoint joinPoint) throws Throwable {

        if (!User.currentUser().isLoggedIn()) {

            Router.getInstance().open("login");
            return;
        }

        joinPoint.proceed();

    }
}

@CheckLogin可以直接在匿名内部类中使用,它会先判断用户是否登录,如果没有登录就会跳转到登录页面。如果已经登录,则处理后面的业务逻辑。

RxView.clicks(favorite)
                .compose(RxJavaUtils.preventDuplicateClicksTransformer())
                .compose(RxLifecycle.bind(ContentDetailActivity.this).toLifecycleTransformer())
                .subscribe(new Consumer<Object>() {

                    @CheckLogin
                    @Override
                    public void accept(@NonNull Object o) throws Exception {
                        ......
                    }
                });

@CheckLogin 也可以结合类似butterknife这样的框架使用。

    @CheckLogin
    @OnClick(id=R.id.button_favorite)
    void onClickFav() {
           ......
    }

总结

本文有一点小小的标题党,其实我所做的修改并不仅仅是为了RxJava。但是RxJava已经变得越来越流行,AOP也能够跟Rx很好地相结合。本文除了记录工作中个人所使用的一些东西,也希望能够起到一些抛砖引玉的作用。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 无论是谁都无法与时间对抗,迟早都会变老。不管你现在离老年生活还有多远,你有没有思考过自己老了以后的生活?当我们老了...
    你我的文字缘阅读 499评论 2 1
  • 有个太太多年来不断抱怨对面的太太很懒惰,“那个女人的衣服永远洗不干净,看,她晾在外院子里的衣服,总是有斑点,我真的...
    songsong_add0阅读 207评论 0 0
  • 这几天随便画的。反正也没人看,干脆自己给自己一个小窝,专门看自己的进步与退步好了。
    野马阿姨阅读 217评论 2 0