含义:
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
常见使用场景:
性能监控: 在方法调用前后记录调用时间,方法执行太长或超时报警。
缓存代理: 缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
软件破解: 使用AOP修改软件的验证类的判断逻辑。
记录日志: 在方法执行前后记录系统日志。
工作流系统: 工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务。
权限验证: 方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉。
一些概念:
Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point:拦截点,如某个业务方法, 表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,即Joinpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint
Advice:Advice 定义了在 pointcut 里面定义的程序点具体要做的操作,即要切入的逻辑,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target:被aspectj横切的对象。我们所说的joinPoint就是Target的某一行,如方法开始执行的地方、方法类调用某个其他方法的代码
下面是一些具体实现方法的介绍及demo:
动态代理:
现在火热的rest api访问库Retrofit就使用了动态代理, 通过在运行期动态的创建代理类。使用动态代理实现AOP需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口反射机制生成一个代理类,然后在这个代理类中织入代码。被代理的类是AOP里所说的目标,InvocationHandler是切面,它包含了Advice和Pointcut。
demo:
动态代理java自身支持,不需要引入外部库,在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,第一代理类必须实现一个接口,如果没实现接口会抛出一个异常。第二性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢
动态字节码生成
在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中,没有接口也可以织入,但扩展类的实例方法为final时,则无法进行织入。
使用Cglib来实现动态字节码技术。Cglib是一个强大的,高性能的Code生成类库,它可以在运行期间扩展Java类和实现Java接口,它封装了Asm,所以使用Cglib前需要引入Asm的jar,这里直接使用gradle编译测试工程,可引入依赖: compile 'cglib:cglib-nodep:3.2.2',包含了asm库
300kb的jar库, 2004年git首次提交, https://github.com/cglib/cglib
自定义类加载器
在运行期,目标加载前,将切面逻辑加到目标字节码里。
可以对绝大部分类进行织入,但代码中如果使用了其他类加载器,则这些类将不会被织入。
Javassist是一个编辑字节码的框架,可以让你很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。这比使用Cglib实现AOP更加高效,并且没太多限制
简单织入可直接使用CtMethod的insertBefore insertAfter方法,也可派生Translator做更复杂的操作
708kb的javassist.jar 至少1999年就有了, 首次github提交在2003年,https://github.com/jboss-javassist/javassist
字节码转换
在运行期,所有类加载器加载字节码前,前进行拦截,可以对所有类进行织入。
使用起来比较麻烦,字节码转换器开发好后,执行代码时还要诸多准备:
- 使用premain函数注册字节码转换器,该方法在main函数之前执行
- 需要告诉JVM在启动main函数之前,需要先执行premain函数。首先需要将premain函数所在的类打成jar包。并修改该jar包里的META-INF\MANIFEST.MF 文件。
- 启动时还要再JVM的启动参数里加上。-javaagent:xxxlib\aop.jar
更详细的做法见下面的参考文章列表,对于android开发不适用,一般app没机会修改jvm启动参数
最火的aspectj
官网地址是:http://www.eclipse.org/aspectj
插件安装地址: http://download.eclipse.org/tools/ajdt/43/update
AspectJ 生成字节码 Supports build time and load time code injection.
apsectj是动态、静态植入结合的: Target() this()就是属于他动态植入的方式,其它如:within是静态植入的, target(),this()需要在运行时才能确定那些被拦截
demo:
http://gitlab.alibaba-inc.com/hongkai.qhk/KaiAnimator/tree/master/aspectj
https://github.com/qianhk/FeiAndroid/tree/master/aspectjDemo
功能强大的有点过分,切入点干净,基本无入侵,但需要用ajc命令来编译java,eclipse有插件ajdt支持。
详细用法见参考文章里的:http://blog.csdn.net/zl3450341/article/category/1169602
maven可参考:http://maven.apache.org/maven-1.x/plugins/aspectj、http://mojo.codehaus.org/aspectj-maven-plugin
gradle有非官方的https://github.com/uPhyca/gradle-android-aspectj-plugin,但与android的databind有冲突,经与作者联系,作者推荐 https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
,经过使用发现要把项目里dependencies里的apt "com.google.dagger:dagger-compiler:2.2" 改成provide, 经过试验修改后dagger功能基本正常。
还有这个文章里提到的方法:http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/,但写法很复杂。
所有插件的核心作用就一个,让java代码使用ajc编译
APT方式
自定义一个AbstractProcessor
总结
重要的是思想,实现方式多种多样,不用这上面所有的方式依然可以实现。
KaiAnimator里为了让学习、测试api等方便点,让测试或者练习不需要都在布局里加个按钮,再添加相应的处理方法,很繁琐,(虽然一些诸如ButterKnife可以简化一点代码,但依然繁琐不便), 使用了Annoation加Reflection的方式实现AOP,每个测试Fragment直接像如下写法就可以了,fragment里都直接是一个个的测试方法即可:
@TestFunction("测试功能说明")
public void test00() {
......
}
参考文章:
http://www.iteye.com/topic/1116696
http://llying.iteye.com/blog/220452
http://blog.csdn.net/innost/article/details/49387395