来源:知乎 欲眼熊猫
面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。 但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。 也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。 一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。 AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。
** 在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。**
场景:
分离复杂度,逻辑事务与非逻辑事务分离
具体场景有:
- 日志收集;
- 权限验证;
- 数据验证;
- 监控;
-
更多
逻辑代码与非逻辑代码示例
基本需要了解的概念
Join Points
JPoints就是程序运行时的一些执行点。那么,一个程序中,哪些执行点是JPoints呢?比如:
l 一个函数的调用可以是一个JPoint。比如Log.e()这个函数。e的执行可以是一个JPoint,而调用e的函数也可以认为是一个JPoint。
l 设置一个变量,或者读取一个变量,也可以是一个JPoint。比如Demo类中有一个debug的boolean变量。设置它的地方或者读取它的地方都可以看做是JPoints。
我们经常看到的依赖注入经常是这样用的:
@BindView(R.id.textView_commend_2)
CommonTabLayout tl_2;
@OnClick(R.id.textView_commend_2)
public void onClickAdd() {
//在该方法运行时 也是执行点
}
上面代码的作用相信大家都知道,那么在这里 设置变量tl_2
时是执行点,声明方法onClickAdd
也是执行点。
甚至 我做一个操作 ,比如点击一个按钮,这个按钮会执行onClickAdd
方法,那么在我调用 onClickAdd
方法之前可以是执行点、在执行onCLickAdd
的过程中也可以是执行点。
这段话说的有点绕口,如果没有表达清楚,请见谅。
<center> AspectJ中的Join Point </center>
Join Points | 说明 | 示例 |
---|---|---|
method call | 函数调用 | 比如调用Log.e(),这是一处JPoint |
method execution | 函数执行 | 比如Log.e()的执行内部,是一处JPoint。注意它和method call的区别。method call是调用某个函数的地方。而execution是某个函数执行的内部。 |
constructor call | 构造函数调用 | 和method call类似 |
constructor execution | 构造函数执行 | 和method execution类似 |
field get | 获取某个变量 | 比如读取DemoActivity.debug成员 |
field set | 设置某个变量 | 比如设置DemoActivity.debug成员 |
pre-initialization | Object在构造函数中做得一些工作。 | |
initialization | Object在构造函数中做得工作 | |
static initialization | 类初始化 | 比如类的static{} |
handler | 异常处理 | 比如try catch(xxx)中,对应catch内的执行 |
advice execution | 这个是AspectJ的内容 | 我自己也不太懂 |
Pointcuts
Pointcuts是什么呢? Pointcuts的功能 是从众多的 JoinPoint中找到指定的执行点;
在上面的代码中 我可以通过注解拿到 变量和方法;然而通过上面的解释我们知道一个变量或者方法存在好多个执行点,我方法在即将执行之前是执行点,执行过程中也可以是个执行点,set变量是执行点,get变量也是执行点,那么我拿到了变量或者方法,我在什么时候做事情呢? 我通过什么判断呢? Pointcuts就是做这个事情的。
** 白话:pointCuts 的作用是找到指定的注解,通过注解拿到声明的 变量或者方法,然后设置 变量/方法 在指定的执行点 joinPoint 。**
然后我们就可以在 指定的执行点下做我们自定义的一系列动作。
** Pointcuts的目标是提供一种方法使得开发者能够选择自己感兴趣的JoinPoints。**
pointcuts中最常用的选择条件和Joinpoint的类型密切相关
题目: 小明的旧娃娃质量不好,没用几下折腾几下就破掉了,小明很郁闷,然后小明去买新娃娃,这个店家给他推荐了一个苍老师版本的娃娃,小明在付款的时候突然想到旧娃娃的质量不好,提出要验验货。‘
** 答案:**
@Pointcut("execution(@com.wenld.aspectjdemo.买娃娃 * *(..))")
public void executionAspectJ() {
验货...嘿嘿嘿
if(验货通过) 买
}
声明我们要找的买娃娃
注解,注解找到的方法 选择苍老师款+付款
(execution
)的时候 ,executionAspectJ
验验货....
advice和aspect
恭喜,看到这个地方来,AspectJ的核心部分就掌握一大部分了。现在,我们知道如何通过pointcuts来选择合适的JPoint。那么,下一步工作就很明确了,选择这些JPoint后,我们肯定是需要干一些事情的。比如前面例子中的 before 小明验货之类的。这其实JPoint在执行前,执行后,都执行了一些我们设置的代码。
advice的类型
关键词 | 说明 | 示例 |
---|---|---|
before() | before advice | 表示在JPoint执行之前,需要干的事情 |
after() | after advice | 表示JPoint自己执行完了后,需要干的事情。 |
after():returning(返回值类型) after():throwing(异常类型) | returning和throwing后面都可以指定具体的类型,如果不指定的话则匹配的时候不限定类型 | 假设JPoint是一个函数调用的话,那么函数调用执行完有两种方式退出,一个是正常的return,另外一个是抛异常。 注意,after()默认包括returning和throwing两种情况 |
返回值类型 around() | before和around是指JPoint执行前或执行后备触发,而around就替代了原JPoint | around是替代了原JPoint,如果要执行原JPoint的话,需要调用proceed |
在Android 中AOP工具和库
AspectJ: 一个 JavaTM 语言的面向切面编程的无缝扩展(适用Android)。
Javassist for Android: 用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版。
DexMaker: Dalvik 虚拟机上,在编译期或者运行时生成代码的 Java API。
ASMDEX: 一个类似 ASM 的字节码操作库,运行在Android平台,操作Dex字节码。
在Android 中AOP的使用
这里使用 AspectJ:
- 功能强大
- 支持编译期和加载时代码注入
- 易于使用
先要装ASpectJ环境 项目中有安装包,在下图位置
安装AspectJ命令 Java -jar Aspectj-xxx.jar
;
path 配置环境变量 c:\xxx\aspectjxx\bin
;
环境搭建好以后
其中还要在gradle内配置一些东西,具体详细请看代码,这里就不贴了。
注解类 AspectJAnnotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AspectJAnnotation {
String value();
}
执行类AspectJTest
/**
* <p/>
* Author: 温利东 on 2017/3/2 16:07.
* blog: http://blog.csdn.net/sinat_15877283
* github: https://github.com/LidongWen
*/
@Aspect
public class AspectJTest {
private static final String TAG = "tag00";
@Pointcut("execution(@com.wenld.aspectjdemo.AspectJAnnotation * *(..))")
public void executionAspectJ() {
}
@Around("executionAspectJ()")
public Object aroundAspectJ(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Log.i(TAG, "aroundAspectJ(ProceedingJoinPoint joinPoint)");
AspectJAnnotation aspectJAnnotation = methodSignature.getMethod().getAnnotation(AspectJAnnotation.class);
String permission = aspectJAnnotation.value();
if(permission.equals("权限A")) {
Object result=joinPoint.proceed();
Log.i(TAG, "有权限:"+permission);
return result;
}
return "";
}
}
MainActivity
@AspectJAnnotation(value = "权限A")
public String test() {
Log.i(TAG, "检查权限");
return "test";
}
编译完成后 会发现多了一个 MainActivity$AjcClosure1.class
类:
MainActivity.class
你会发现 注入了一些方法;
示例源码地址:https://github.com/LidongWen/AspectJDemo/tree/master
** 一些资料地址:**
https://en.wikipedia.org/wiki/AOP
l http://www.eclipse.org/aspectj/ <=AspectJ官方网站
l http://www.eclipse.org/aspectj/doc/released/runtime-api/index.html <=AspectJ类库参考文档,内容非常少
l http://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/index.html <=@AspectJ文档,以后我们用Annotation的方式最多。
AspectJ语法
http://www.eclipse.org/aspectj/doc/released/quick5.pdf 或者官方的另外一个文档也可以:
http://www.eclipse.org/aspectj/doc/released/progguide/semantics.html