当RxJava遇到AOP

简介: 当RxJava遇到AOP

背景



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


image.png

@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很好地相结合。本文除了记录工作中个人所使用的一些东西,也希望能够起到一些抛砖引玉的作用。

相关文章
|
Java Spring
AopContext.currentProxy();为什么能获取到代理对象
AopContext.currentProxy();为什么能获取到代理对象
437 0
最通俗易懂的 JAVA slf4j,log4j,log4j2,logback 关系与区别以及完整集成案例
最通俗易懂的 JAVA slf4j,log4j,log4j2,logback 关系与区别以及完整集成案例
最通俗易懂的 JAVA slf4j,log4j,log4j2,logback 关系与区别以及完整集成案例
Java8中的函数式接口详解(Supplier、Consumer、Predicate、Function)
Java8中的函数式接口详解(Supplier、Consumer、Predicate、Function)
1379 1
|
3天前
|
存储 弹性计算 人工智能
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
2025年9月24日,阿里云弹性计算团队多位产品、技术专家及服务器团队技术专家共同在【2025云栖大会】现场带来了《通用计算产品发布与行业实践》的专场论坛,本论坛聚焦弹性计算多款通用算力产品发布。同时,ECS云服务器安全能力、资源售卖模式、计算AI助手等用户体验关键环节也宣布升级,让用云更简单、更智能。海尔三翼鸟云服务负责人刘建锋先生作为特邀嘉宾,莅临现场分享了关于阿里云ECS g9i推动AIoT平台的场景落地实践。
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
|
2天前
|
云安全 人工智能 自然语言处理
阿里云x硅基流动:AI安全护栏助力构建可信模型生态
阿里云AI安全护栏:大模型的“智能过滤系统”。
|
2天前
|
人工智能 自然语言处理 自动驾驶
关于举办首届全国大学生“启真问智”人工智能模型&智能体大赛决赛的通知
关于举办首届全国大学生“启真问智”人工智能模型&智能体大赛决赛的通知
|
3天前
|
Linux 虚拟化 iOS开发
VMware Workstation Pro 25H2 for Windows & Linux - 领先的免费桌面虚拟化软件
VMware Workstation Pro 25H2 for Windows & Linux - 领先的免费桌面虚拟化软件
871 4
VMware Workstation Pro 25H2 for Windows & Linux - 领先的免费桌面虚拟化软件
|
5天前
|
存储 机器学习/深度学习 人工智能
大模型微调技术:LoRA原理与实践
本文深入解析大语言模型微调中的关键技术——低秩自适应(LoRA)。通过分析全参数微调的计算瓶颈,详细阐述LoRA的数学原理、实现机制和优势特点。文章包含完整的PyTorch实现代码、性能对比实验以及实际应用场景,为开发者提供高效微调大模型的实践指南。
577 2