背景
公司打算开发一款全新的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(),符合预期。
@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很好地相结合。本文除了记录工作中个人所使用的一些东西,也希望能够起到一些抛砖引玉的作用。