Aspect和AOP打点调研
面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是都是计算机编程架构。OOP在开发中,更多的是用抽象思维将一切事物都抽象为对象,学生类,订单类甚至是图形类,所谓的面向切面编程其实是对业务逻辑又进行了进一步的抽取,将多种业务逻辑中的公用部分抽取出来做成一种服务(比如日志记录,性能统计,安全验证等),从而实现代码复用。
而这些抽象的基础就是这些属性或者操作是固定的,如学生的事务中的登录,查询成绩等等,可是这些真的是固定的么?
我们将一切对象都可以定义成类,相同的可以聚合成一个父类,不同的逻辑可以交给多态或者继承。
而有些逻辑这很难用这OOP去解决,如打点问题,假设我们项目分为多个功能模块或者业务模块,而这些这些模块依赖基础模块。
假如哪一天PM要求我们给各个业务模块打点(如点击事件打点),那么我们的思路肯定在每个模块下一个个打点,针对不同的模块,不同的view进行打点,主要是这些打点逻辑还是重复,和业务实际上关联性很弱,也就是说没它程序也没跑起来,只是在相同或相似时机(如打点模块的点击)去触发打点逻辑。如图所示:
这样机械性的工作,是不是可以考虑AOP呢?
那么什么是AOP呢?
AOP
AOP是Aspect Oriented Programming的缩写,中译文为面向切向编程,他是对OOP的补充。
如上面说的打点的例子,就可以通过AOP很好实现,每个功能模块或者业务模块不需要改动,只需要将打点的逻辑上做一个切面,通过织入需要的代码即可,若你不理解可以看下面的例子。
对函数做切面
我们写个简单的demo,有个类,里面只有onClick和onCreate方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button textView = null;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.tvjj);
textView.setOnClickListener(this);
}
public void onClick(View v) {
Log.d("Aop", "onClick");
}
}
我们给这个类加切面:
1 |
|
运行后的日志:
1 | 2019-06-23 18:56:21.661 3091-3091/cc.cyning.aspectjx E/AspectTest: ----- |
在MainActivity
里我们什么也没做,只是在AspectTest
里写了加了一个切面,在执行MainActivity
里面的on**
(里面只有onCreate
,onClick
)前加入了打印当前类和方法的,就可以在onCreate
和onClick
执行时打印出相关信息,是不是很神奇。
那么怎么怎么做切面,切面逻辑怎么写?下面就是我们的主角AspectJ
AspectJ
AspectJ是一个代码织入技术(code injection),提供了一套全新的语法实现,完全兼容Java(其实跟Java之间的区别,只是多了一些关键词而已)。同时,还提供了纯Java语言的实现,通过注解的方式,完成代码编织的功能。因此我们在使用AspectJ的时候有以下两种方式:
- 使用AspectJ的语言进行开发
- 通过AspectJ提供的注解在Java语言上开发
在了解AspectJ的具体使用之前,先了解一下其中的一些基本的术语概念
常用术语
下面的一些常用术语是我们开发中必须知道的,我们自己做切面时这些术语
Join Points
程序中可能作为代码注入目标的特定的点。join point 可以包含其它 join point。例如,一个方法调用可能在它返回之前引起其它方法调用。那么,Pointcut 就是一种语言构造,这种构造根据已定义的标准挑选一组 join point。
其常用的如下:
JoinPoints | 说明 | 示例 |
---|---|---|
method call | 函数调用 | 比如调用Log.e(),这是一处Joint point |
method execution | 函数执行 | 如实例中的onCreate的执行时,其内部代码 |
constructor call | 构造函数调 | 与方法的调用类型 |
constructor | executor | 构造函数执行 与方法的执行执行 |
field get | 获取某个变量 | |
field set | 设置某个变量 | |
static initialization | 类初始化 | |
initialization | object在构造函数中做的一些工作 | |
handler | 异常处理 | 对应try-catch()中,对应的catch块内的执行 |
Pointcuts
代码注入的位置,这个注意是个点或者多个调用点。
Advice
其实就是注入到class文件中的代码片,其他告知注入的位置,如是在方法前(Before),还是在方法后(After),或者是在体制替换整个方法(Around)
切点语法表达式
可以拿刚才的例子来学习;
1 | "execution(* *..MainActivity+.on**(..))") ( |
可以知道是
- 切点语法表达式 = Advice(Before属于Advice)+ Pointcuts
如1
2
3
4"onClickPointcuts() || onClickInXmlPointcuts() || onClickInButterKnifePointcuts()") (
public void throttleClick(ProceedingJoinPoint joinPoint) throws Throwable {
}
onClickPointcuts
就是一个Pointcut
- Pointcuts = 可能是多个Pointcut
Pointcut = Join Point的关键字(call,execution) + 函数,变量或者其他切入点
1 | "execution(* android.view.View.OnClickListener.onClick(..))") ( |
那么这些*
究竟代表啥呢?
若是之间接触过正则表达式,可能就很容易了。
|表达式 |含义|
|—|—|
|java.lang.String |匹配String类型|
|java..String | 匹配java包下的任何“一级子包”下的String类型,如匹配java.lang.String,但不匹配java.lang.ss.String|
|java.. | 匹配java包及任何子包下的任何类型,如匹配java.lang.String、java.lang.annotation.Annotation
|java.lang.*ing| 匹配任何java.lang包下的以ing结尾的类型|
|java.lang.Number+ |匹配java.lang包下的任何Number的自类型,如匹配java.lang.Integer,也匹配java.math.BigInteger|
常用的AspectJ 切点语法表达式
call和execution
call
表示的外接的调用,而execution
则是函数内部。
MainActivity.class
测试execution
1 | ·"execution(* cc.cyning.aspectjx.MainActivity.onTest(..))") ( |
返回的结果:
1 | 8828-8828/cc.cyning.aspectjx D/Aop: onClick before |
测试call
1 | "call(* cc.cyning.aspectjx.MainActivity.onTest(..))") ( |
运行结果:
1 | 9003-9003/cc.cyning.aspectjx D/Aop: onClick before |
可以知道call
上是运行的35行,而execution
是运行在onTest
的内部的.
对于更多的ASpectJ
的内容,可以查看我文章末尾的参考文章。
还是回归到打点的问题。
view的点击打点
具体思路是想可以参考网易HubbleData之Android无埋点实践
那么我们可以先使用ASpectJ
来处理点击view的打点。
对于view我们最简单的是在View.OnClickListener.onClick切面上加入打点逻辑,当时现在很多点击时间还真的不再这样实现。
问题:
在xml的中设置点击事件
1
2
3
4
5
6
7
8
9
10<Button
android:id="@+id/button"
android:onClick="onClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>ButterKnife注释实现点击事件
1
2(R.id.button)
public onClickView(View view)lambda的表达式实现点击事件
1 | button.addClickListener(clickEvent -> doSth()); |
xml中点击事件
对于这个问题,最简单的就是看view的代码,不多说,看下源码:
1 | final TypedArray a = context.obtainStyledAttributes(attrs, sOnClickAttrs); |
可以在DeclaredOnClickListener
做动作吧。
ButterKnife
不多说,可以看代码解决。
可以去拦截butterknife.OnClick
下面的监听事件
参考
- 本文链接:http://ownwell.github.io/2018/12/23/Aspect和aop打点调研/
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!