流行框架源码分析(10)-AOP在代码中的使用分析
主目录见:Android高级进阶知识(这是总目录索引)
AOP(Aspect-oriented programming)面向切面编程,如果你关注到这个概念的话,那你应该大概懂得他的作用了。最早接触这个概念是在Spring框架中,如今android也越来越多地地方可以使用他了,其实他不是一门语言,他是一种编程思想。今天我们不讲基础的概念,如果你还不了解,这里推荐几篇文章:
1.Android中的AOP编程;
2.Aspect Oriented Programming in Android这是前一篇的英文版本;
3.深入理解Android之AOP ;
4.同时这里推荐一个项目hugo (一个日志打印框架)。
一.目标
AOP的实现框架有好多,我们今天选择一个AOP库AspectJ来实现我们的功能,我们今天还是以我们的框架LRouter来讲解。今天有如下目标:
1.了解AOP的思想概念;
2.懂得简单地AOP库AspectJ的使用。
二.使用讲解
首先讲解之前我来说一下为什么当初会用到这个,因为当初写这个框架有个需求是:
因为框架是多组件跨进程的模块分割的,然后我们想知道各个模块之间的通讯频率,还有哪个模块被访问的次数最多呢?这时候我们不能再对原有的框架做一个大的改动,像ARouter用的是CountDownLatch机制来做的拦截。但是我们这里明显不适用,我们只能选择一个侵入性比较小的办法,那就是这里的AOP了,我们在跨进程请求的部分做了一个拦截,拦截了请求参数,请求的方法等等信息来做一个记录。
1.gradle文件的配置
我们的AspectJ库必须使用 AspectJ 的编译器(ajc,一个java编译器的扩展)对所有受 aspect 影响的类进行织入。所以我们需要一些配置,在hugo中这个配置是写成gradle插件了。我们这里就直接添加到LRouter项目中的lrouter-api项目的gradle中:
gradle文件
具体配置内容在前面文章和项目里面有。
2.Aspect(切面)的选择
我们知道,我们跨进程或者跨模块的访问是在lrouter-api模块的LocalRouter类中的navigation()方法进行访问的,所以我们可以拦截这个方法的访问,我们选择跟hugo同样的做法,在这个方法上面添加一个注解@Navigation:
@Navigation
public ListenerFutureTask navigation(Context context, LRouterRequest request) throws Exception {
........
}
然后我们就可以来定义Pointcut切入点了。
//筛选出用Navigation注解的所有方法
private static final String POINTCUT_METHOD = "execution(@com.lenovohit.lrouter_api.intercept.ioc.Navigation * *(..))";
//筛选出所有用Navigation注解的所有构造函数
private static final String POINTCUT_CONSTRUCTOR = "execution(@com.lenovohit.lrouter_api.intercept.ioc.Navigation *.new(..))";
@Pointcut(POINTCUT_METHOD)
public void methodAnnotationWithNavigation(){}
@Pointcut(POINTCUT_CONSTRUCTOR)
public void constructorAnnotaionWithNavigation(){}
可以看到我们选择了添加了Navigation注解的所有方法和构造函数作为切入点。后面我们就可以选择合适的Advice了,我们这里选择Around这个类型。
@Around("methodAnnotationWithNavigation() || constructorAnnotaionWithNavigation()")
public Object requestAndExecute(ProceedingJoinPoint joinPoint) throws Throwable{
//执行方法前
enterRequestMethod(joinPoint);
//方法执行
long startNanos = System.nanoTime();
Object result = joinPoint.proceed();
long stopNanos = System.nanoTime();
long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);
//执行方法后
exitRequestMethod(joinPoint,lengthMillis);
return result;
}
我们看到这里我们定义了Around类型的Advice,然后我们在方法里在拦截的方法前执行了enterRequestMethod(),还有在方法执行后调用了exitRequestMethod()方法,我们来看下:
public void enterRequestMethod(JoinPoint joinPoint){
CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
String methodName = codeSignature.getName();
String[] parameterNames = codeSignature.getParameterNames();
Object[] parameterValues = joinPoint.getArgs();
if (null != mAopInterceptors) {
for (AopInterceptor aopInterceptor : mAopInterceptors){
aopInterceptor.enterRequestIntercept(methodName, parameterNames, parameterValues);
}
}
}
我们看到这个方法里面通过JoinPoint获取到了方法名,方法参数,参数值等信息,然后调用我们自己的方法进行处理。我们这里是用一个抽象方法来实现,意思就是说给用户自己来处理:
public abstract class AopInterceptor {
/**
* 进入请求方法前
* */
public abstract void enterRequestIntercept(String methodName,String[] paramNames,Object[] paramValues);
/**
* 执行完请求方法后
* */
public abstract void exitRequestIntercept(String methodName,String[] paramNames,Object[] paramValues,long lengthMillis);
}
到这里我们的Aop使用例子也就结束了,步骤还是非常简单的。这个思想还能干很多牛逼的事情,希望大家想不到解决方案的时候,这可以当做一个备选。
总结:今天的例子是非常简单的,因为这个库用起来本来就比较容易,但是思想还是非常棒的,如果大家有兴趣希望进一步了解,看看我上面推荐的几篇文章。