是什么?
aop
的思想是,针对业务处理过程中的切面进行提取,是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果
简单讲,使用的时候是关注具体的方法和功能切入点, 不用关心所在的类或者对象, 只关注功能的实现
有什么用?
举个栗子
现在有个需求:实现监听每个方法耗时并且输出日志
oop
的观念里面我们首先实现一个log
类,然后在每个方法里面记录开始和结束的时间并计算输出。哇,这样一来不仅代码量和复杂程度上面会有所增加,功能模块和log
模块会耦合更重要的是,这种做法好像不是很吊
利用aop
的思想就能很好的解决上述的痛点:把分散的功能集中起来统一管理,降低手动代码量,解耦。具体怎么实现下面会详细讲解
怎么用?
Android中实现AOP
有三种方式:
AspectJ
-
APT
现已被annotationProcessor
替代 Javassist
一一介绍:
AspectJ
AspectJ
是AOP
的Java语言的实现 ,一个代码生成工具
使用AspectJ
的两种方式:
- 完全使用
AspectJ
,相比较Java
只是多了一些关键字 - 纯
Java
语言开发,然后@AspectJ
注解 一般我们都是以这种方式开发
实栗分析
hugo
Hugo
是用于性能优化的第三方库,其作用是可以计算每个方法的耗时并输出,实现方式是AspectJ
Hugo
的项目结构:
Hugo
的源码:
@Aspect
public class Hugo {
private static volatile boolean enabled = true;
@Pointcut("within(@hugo.weaving.DebugLog *)") // @Pointcut 相当于条件判断,哪些方法或者类需要注入代码
public void withinAnnotatedClass() {}
@Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")
public void methodInsideAnnotatedType() {}
@Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")
public void constructorInsideAnnotatedType() {}
@Pointcut("execution(@hugo.weaving.DebugLog * *(..)) || methodInsideAnnotatedType()")
public void method() {}
@Pointcut("execution(@hugo.weaving.DebugLog *.new(..)) || constructorInsideAnnotatedType()")
public void constructor() {}
public static void setEnabled(boolean enabled) {
Hugo.enabled = enabled;
}
@Around("method() || constructor()")
public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {
enterMethod(joinPoint);
long startNanos = System.nanoTime(); //方法的开始时间
Object result = joinPoint.proceed();
long stopNanos = System.nanoTime(); //方法的结束时间
long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);
exitMethod(joinPoint, result, lengthMillis);
return result;
}
private static void enterMethod(JoinPoint joinPoint) {
if (!enabled) return;
// ...
//组织相关信息到builder当中
if (Looper.myLooper() != Looper.getMainLooper()) {
builder.append(" [Thread:\"").append(Thread.currentThread().getName()).append("\"]");
}
Log.v(asTag(cls), builder.toString());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
final String section = builder.toString().substring(2);
Trace.beginSection(section);
}
}
private static void exitMethod(JoinPoint joinPoint, Object result, long lengthMillis) {
if (!enabled) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
Trace.endSection();
}
// ...
//组织相关信息为builder
Log.v(asTag(cls), builder.toString());
}
private static String asTag(Class<?> cls) {
if (cls.isAnonymousClass()) {
return asTag(cls.getEnclosingClass());
}
return cls.getSimpleName();
}
}
对以上的语法做一些解释:
-
@Aspect
表示这个类由AspectJ
处理 -
@Pointcut
描述切面内容,可以理解为针对哪些方法,类,进行拦截,插入代码 -
@Around
实际上拦截方法,这个注解可以同时拦截方法的执行前后,另外有@Before
,@After
,顾名思义,表示方法执行前跟方法执行后拦截
关于Annotation
(注解):
上述hugo-annotations
有着这样的代码:
@Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(CLASS)
public @interface DebugLog {
}
-
@Retention
和@Target
是Java
四种元注解的其中两个,所谓元注解就是注解的注解,显然DebugLog
在使用的过程中是一种注解的运用形式 -
@Retention(CLASS)
是指 ,注解会在class
字节码文件中存在,但运行时无法获得,即无法通过反射获取到 -
@Target(ElementType.TYPE)
目标是,接口、类、枚举、注解 -
@Target(ElementType.METHOD)
目标是,方法 -
@Target(ElementType.CONSTRUCTOR)
目标是,构造函数
APT,annotationProcessor
作用: 编译时期扫描处理源代码中的注解信息, 可以根据注解信息生成一些文件 ,利用APT
为我们生成的Java
代码,实现冗余的代码功能,这样就减少手动的代码输入,提升了编码效率,而且使源代码看起来更清晰简洁
简单的讲,可以生成额外的代码,减少手动输入代码
其实上述的功能有其他的实现方式,比如反射或者动态代理,但是众所周知,反射的性能极差,这也是APT
的价值体现
很多第三方库使用了apt实现aop:Dagger
、ButterKnife
、AndroidAnnotation
、EventBus
可以说基于Java Annotation的自定义注解配合APT实现了很多简单易用性能优越的AOP用法的开源库
Javassist
Javassist
是一个开源的分析、编辑和创建Java字节码的类库
作用:在编译器间修改class
文件
应用场景
一些常见的使用场景:
日志
持久化
性能监控 hugo
数据校验
缓存