AOP 面向切面编程
概念
大家都知道OOP,即ObjectOriented Programming,面向对象编程。而本文要介绍的是AOP。AOP是Aspect Oriented Programming的缩写,中译文为面向切向编程。OOP和AOP是什么关系呢?首先:
在OOP的世界中,问题或者功能都被划分到一个一个的模块里边。每个模块专心干自己的事情,模块之间通过设计好的接口交互
OOP和AOP都是方法论。我记得在刚学习C++的时候,最难学的并不是C++的语法,而是C++所代表的那种看问题的方法,即OOP。同样,今天在AOP中,我发现其难度并不在利用AOP干活,而是从AOP的角度来看待问题,设计解决方法。这就是为什么我特意强调AOP是一种方法论的原因
###AOP
Java 语言中,从织入切面的方式上看,存在三种织入方式:编译期织入、类加载期织入和运行时织入。
编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中;而类加载织入则指通过通过特殊的类加载器,在类字节码加载到JVM时,织入切面;运行期织入则是采用CGLib工具或者JDK动态代理进行切面的织入。
所以,AOP 代理则可分为静态代理和动态代理两大类
- 静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;典型代表Aspectj。
- 动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
静态代理分为:编译时织入(特殊编译器实现)、类加载时织入(特殊的类加载器实现)。
动态代理有 : JDK动态代理(基于接口来实现)、CGlib(基于类实现,弥补JDK动态代理只能代理接口的不足)
名称 | 代理类型 | 基本原理 | 特性 |
---|---|---|---|
AspectJ | 静态代理 | 编译期织入、LTW | |
Cglib | 动态代理 | 运行时动态生成,使用字节码处理框架ASM来转换字节码并生成新类 | cglib可以动态代理类、接口 |
JDK动态代理 | 动态代理 | 代理类在运行时动态生成,源码级别会调用一个Native方法 | 只能代理接口 |
Aspectj
AspectJ 作为 AOP 编程的完全解决方案,主要原理是用asm做字节码替换达到AOP的目的。
AspectJ提供了两种切面织入方式,共三种织入时机,分别为
- 通过特殊编译器,在编译期,将Aspectj语言编写的切面织入到Java类中,可以通过Maven或者Ant任务来完成
- compile-time:编译期织入,将aspectj类和目标类放在一起用ajc 编译,直接编译出包含织入代码的 .class 文件
- post-compile:编译后织入,目标类已经被打成jar包了,这时候也可以用ajc命令将jar再织入一次。增强已经编译出来的类,如我们要增强依赖的 jar 包中的某个类的某个方法
- 类加载期织入,也简称LTW(Load Time Weaving):在 JVM 进行类加载的时候做字节码的替换,完成织入
编译期织入可以理解为静态织入,因为在class
文件生成后,就已经织入好了。类加载期织入,可以理解为“动态织入”(不同与java动态代理的“动态”),因为这个类替换是在jvm
加载类时完成的。
类加载期织入,又有三种方式:
agent
方式:使用jdk5 JVMTI
技术,将aspectjweaver.jar
作为JVMTI
的agent启动jvm启动时加参数替换默认的系统类加载器(AppClassLoader -> WeavingURLClassLoader):
-Djava.system.class.loader=org.aspectj.weaver.loadtime.WeavingURLClassLoader
自定义类加载器:可以自定义类加载器,比如tomcat在加载war包时,可以指定特定的,自定义的类加载器
其中第一种方式对平台的侵入性最小,其实现步骤:
- 通过JVM的
-javaagent:<path_to_aspectj_lib>/aspectjweaver.jar
参数设置LTW的织入器类包,以代理JVM默认的类加载器; - LTW织入器需要一个
aop.xml
文件,在该文件中指定切面类和进行切面织入的目标类; - 把写好的aspectj编译成class文件,并和xml文件一起放在
classpath
里面。这样在加载类的时候就会做织入
第二种方式只是替换了AppClassLoader,不适合osgi平台
第三种方式需要自定义类加载器,实现上复杂一些,但是好处是可以更精细化的控制织入哪些应用的类。比如tomcat加载多个war包,通过这种方式,就可以控制某些war做aop织入,这种粒度的控制则在第一种方式中比较难实现
Aspectj LTW 实践
至此,已经可以通过使用命令行的方式编写AspectJ程序了——AspectJ安装目录下的bin中的“ajc”为编译器(对应Java的“javac”)
- Add
/Users/sloong/Library/aspectj/lib/aspectjrt.jar
to your CLASSPATH. This small .jar file contains classes required by any program compiled with the ajc compiler. - Modify your PATH to include
/Users/sloong/Library/aspectj/bin
. This will make it easier to run ajc and ajbrowser.
1 | export PATH="/Users/sloong/Library/aspectj/bin:$PATH" |
-outxmlfile
Spring AOP
Spring AOP通过JDK Proxy和CGLIB Proxy两种方法实现代理。
如果target object没有实现任何接口,那么Spring将使用CGLIB来实现代理。CGLIB是一个开源项目,它是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
如果target object实现了一个以上的接口,那么Spring将使用JDK Proxy来实现代理,因为Spring默认使用的就是JDK Proxy,并且JDK Proxy是基于接口的。这也是Spring提倡的面向接口编程。当然,你也可以强制使用CGLIB来进行代理,但是这样可能会造成性能上的下降。
启用CGLib 实现代理,需要增加以下配置
1 | true = |
Spring AOP 只能作用于 Spring 容器中的 Bean,它是使用纯粹的 Java 代码实现的,只能作用于 bean 的方法。
参考
深入理解Android之AOP https://blog.csdn.net/innost/article/details/49387395
export PATH=”/Users/sloong/Library/aspectj/bin:$PATH”source ~/.zshrcajc -v #可以检测是否安装配置成功properties