前言
在JAVA编程中,AOP技术被广泛运用。其中较为熟悉的Spring AOP被大家广泛运用。
AOP底层技术
下面是我们常用AOP技术的。
技术方式 | 控制泛围 | 性能 | 工作原理 |
---|---|---|---|
直接改写class文件 | 类 | 无明显性能代价 | 对 class 文件结构和 Java 字节码《class文件和字节码解析》 |
JDK Instrument(探针) | 类 | 无论是否改写,每个类装入时都要执行 hook 程序 | 通过JVM 提供hook程序拦截每个类装载过程,并对目标类修改 |
JDK Proxy | 接口 | 反射引入性能代价 | 通过JVM提供方式,查找到JAVA对象对应的方法的执行地址《Java对象内存表示机制》 |
ASM | 类 | 无明显性能代价 | 通过类操作的方式,对应生成JAVA字节码 |
Javassist | 类 | 无明显性能代价 | 通过编写类的方式,然后编译成字节码,达到字节码修改的目的 |
对于上述几种方式的DEMO如下:
- 直接改写class文件(略)
- JDK Instrument(待续)
- JDK Proxy (略)
- ASM
public class ASMTest extends ClassLoader {
public Class<?> defineMyClass( byte[] b, int off, int len)
{
return super.defineClass(b, off, len);
}
public static void main(String[] args) throws Exception {
ClassWriter classWriter = new ClassWriter(0);
// 通过visit方法确定类的头部信息
classWriter.visit(Opcodes.V1_7,// java版本
Opcodes.ACC_PUBLIC,// 类修饰符
"Programmer", // 类的全限定名
null, "java/lang/Object", null);
//创建构造函数
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 定义code方法
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",
null, null);
methodVisitor.visitCode();
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
classWriter.visitEnd();
// 使classWriter类已经完成
// 将classWriter转换成字节数组写到文件里面去
byte[] data = classWriter.toByteArray();
File file = new File("./target/classes/Programmer.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
Class clazz = new ASMTest().defineMyClass(data,0, data.length);
System.out.println(clazz.getSimpleName());
Object object = clazz.newInstance();
Method method1 = clazz.getDeclaredMethod("code");
method1.invoke(object);
}
}
- Javassist
public class JavassistTest extends ClassLoader{
public Class<?> defineMyClass( byte[] b, int off, int len)
{
return super.defineClass(b, off, len);
}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//创建Programmer类
CtClass cc= pool.makeClass("Programmer");
//定义code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法代码
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
//保存生成的字节码
cc.writeFile("./stream/target/classes");
InputStream input = new FileInputStream("./stream/target/classes/Programmer.class");
byte[] data = new byte[1024];
int count = input.read(data);
Class clazz = new JavassistTest().defineMyClass(data,0, count);
System.out.println(clazz.getSimpleName());
Object object = clazz.newInstance();
Method method1 = clazz.getDeclaredMethod("code");
method1.invoke(object);
}
}
cglib代理技术
从上面分析得。ASM技术,Javassist技术运行速度上明显优于JDK proxy。但操作性远远不如。因此有人整理封装ASM,得到我们的cglib.简单看其通过继承方式的DEMO.
public class CglibTest {
static class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
System.out.println("Before: "+ method);
Object object = proxy.invokeSuper(obj, arg);
System.out.println("After: "+method);
return object;
}
}
static class ElectricCar {
public void drive() {
System.out.println("Electric Car is Moving silently...");
}
public void recharge() {
System.out.println("Electric Car is Recharging...");
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ElectricCar.class);
enhancer.setCallback(new MyMethodInterceptor());
ElectricCar student = (ElectricCar)enhancer.create();
student.drive();
student.recharge();
}
}
ps: spring 在 3.20之后,把asm,cglib收于 spring-core中。
aop alliance
aop和java有浓厚兴趣的软件开发人员联合成立的开源项目,主要定义AOP的统一规范。主要提出2概念
- Joinpoint 程序在执行某个方法时的连接点
- ConstructorInvocation 构造执行点
- MethodInvocation 方法执行点
- Advice 连接点(Joinpoint)监听器,可以对任意连接点进行增强处理
- ConstructorInterceptor 构造执行监听
- MethodInterceptor 方法执行监听
ps: aop alliance项目, 被移入 spring-aop中
AspectJ
细化了AopAlliance对Advice的定义。
- CutPoint 切入点,指符合要求的连接点
- Advice 切入点监听器,可以对切入接点进行增强处理
- Aspect 切面,汇聚相关业务切入点形成面
并进一步对Advice细分:
//除了Advice原生的Around事件以外,另加入如下几个切点
try{
try{
//@Before
method.invoke(..);
}finally{
//@After
}
//@AfterReturning
}catch(){
//@AfterThrowing
}
AspectJ技术原理
AspectJ对类代理的原理是直接操作字节。但操作字节码分别有以下几种情况。
- 编译阶段
- 加载阶段, “classLoader” 和 “Instrument ‘, 又称 加载时编入(Load-Time-Weaving),简称LTW.
- 运行阶段, ”Instrument“
针对1阶段给出例子如下
<!--maven 编译插件-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.10</version>
</plugin>
<!--maven 依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.1</version>
</dependency>
//引出 aj文件
public aspect MyAspect{
/**
* 切入点:SampleServiceImpl所有 public 且以 add 开头的方法
*/
public pointcut serviceAddMethods(): execution(public * com.aspectJ.aspectJDemo.SampleServiceImpl.add*(..));
Object around(): serviceAddMethods(){
Object oldValue = proceed();
System.out.println("add方法执行结果:" + oldValue);
return Integer.MIN_VALUE;
}
}
public class SampleServiceImpl {
public int add(int x, int y) {
return x + y;
}
}
public class AppTest {
public static void main(String[] args) {
SampleServiceImpl service = new SampleServiceImpl();
service.add(2, 4);
}
}
控制台输出结果为:
add方法执行结果:6
查看target/classes会发现如下结果:
//编译aj为class文件
@Aspect
public class MyAspect {
static {
try {
ajc$postClinit();
} catch (Throwable var1) {
ajc$initFailureCause = var1;
}
}
public MyAspect() {
}
@Around(
value = "serviceAddMethods()",
argNames = "ajc$aroundClosure"
)
public Object ajc$around$com_aspectJ_aspectJDemo_MyAspect$1$f211abe7(AroundClosure ajc$aroundClosure) {
Object oldValue = ajc$around$com_aspectJ_aspectJDemo_MyAspect$1$f211abe7proceed(ajc$aroundClosure);
System.out.println("add方法执行结果:" + oldValue);
return -2147483648;
}
public static MyAspect aspectOf() {
if (ajc$perSingletonInstance == null) {
throw new NoAspectBoundException("com_aspectJ_aspectJDemo_MyAspect", ajc$initFailureCause);
} else {
return ajc$perSingletonInstance;
}
}
public static boolean hasAspect() {
return ajc$perSingletonInstance != null;
}
}
//将代码切入SampleServiceImpl中实现代理
public class SampleServiceImpl {
public SampleServiceImpl() {
}
public int add(int x, int y) {
return Conversions.intValue(add_aroundBody1$advice(this, x, y, MyAspect.aspectOf(), (AroundClosure)null));
}
}
针对2阶段给出例子。
<!--maven 依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.1</version>
</dependency>
public class SampleServiceImpl {
public int add(int x, int y) {
return x + y;
}
}
public class ClassLoaderTest extends WeavingURLClassLoader {
public ClassLoaderTest(ClassLoader parent) {
super(parent);
}
public Class defineClass(String name, byte[] b, CodeSource cs) throws IOException {
return super.defineClass(name,b,cs);
}
public static void main(String[] args) throws Exception {
//例子1中,编译出来MyAspect存入的路径
System.setProperty("aj.aspect.path", "/Users/moyao/IdeaProjects/AspectJDemo/src/test/java");
ClassLoaderTest weavingURLClassLoader = new ClassLoaderTest(ClassLoaderTest.class.getClassLoader());
//取出现在已编译好的原生的SampleServiceImpl class文件
InputStream input = new FileInputStream("./target/classes/com/aspectJ/aspectJDemo/SampleServiceImpl.class");
int count = input.available();
byte[] data = new byte[count];
input.read(data);
//向class文件织入代理代码
Class clazz = weavingURLClassLoader.defineClass("com.aspectJ.aspectJDemo.SampleServiceImpl", data, null);
//执行add方法
Object object = clazz.newInstance();
Method method1 = clazz.getDeclaredMethod("add", int.class, int.class);
method1.invoke(object, new Object[]{4, 4});
}
}
控制台输出结果为:
add方法执行结果:8
可见AspectJ可分为2步骤:
- 编译aj文件,生成Aspect class文件
- 根据Aspect class文件切入代理类
针对”加载“和”运行“阶段的探针(Instrument)例子:
《Java SE 6 新特性Instrumentation》
主要参考
《AOP 的利器:ASM 3.0 介绍》
《JAVA instrument简单使用》
《Java动态代理机制详解》
《javassist使用全解析》
《Java SE 6 新特性Instrumentation》
《Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving》
《Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细讲》
《AspectJ简单Demo》