javassist实现动态字节码

AOP编程思想

AOP的翻译是面向切面编程。单从字面意思可能不太好理解,接下来我会简单讲解。

什么情况下,我们需要AOP?

举一个例子,现在你有以下类:

1
2
3
4
5
6
7
8
9
public class test {
public Object createTest() {
// 逻辑...
}

public Object deleteTest() {
// 逻辑...
}
}

你好不容易打好了上面的业务逻辑,这时候,甲方突然要求为这个类的所有可见方法添加鉴权。鉴权代码已经有了。

1
2
3
4
5
public class test2 {
public static boolean isSafe(test t) {
// 逻辑...
}
}

这时候怎么办?难道说要在上面test类里的所有方法前加上test2.isSafe(this)这一坨吗?这还只有两个方法,如果不止两个呢?万一以后要维护加功能呢?

这时候,AOP的优点就体现出来了。

AOP的思想,就是将代码插入某一段代码中。如下例,可以通过JDK动态代理的方式动态将代码插入执行

1
2
3
4
5
6
7
public class testProxy {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
test2.isSafe(/*something*/);
Object obj = method.invoke(sql, args); // 这里就是动态执行的地方
return obj;
}
}

还要另外一种方式便是动态的字节码

Javassist

通过下例来学习Javassist的基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import javassist.*;

public class JavasTest {
public static void main(String[] args) throws Exception {

// ClassPool是单例模式的,只能通过getDefault获取
ClassPool cp = ClassPool.getDefault();

// 创造一个空类TestMy
CtClass cc = cp.makeClass("JavassistTest.TestMy");

// 为TestMy创造一个String name属性,可见性是private
// 创建属性
CtField ccName = new CtField(cp.get("java.lang.String"), "name", cc);
// 设置可见性
ccName.setModifiers(Modifier.PRIVATE);
// 设置初始值为test
cc.addField(ccName, CtField.Initializer.constant("test"));

// 为testMy创建一个无参构造方法
// 其中new CtClass[]{}为没有参数
CtConstructor ctc1 = new CtConstructor(new CtClass[]{}, cc);
// 设置其代码
ctc1.setBody("{System.out.println(111);$0.name = \"kksk\";}");
// 正式添加这个构造方法
cc.addConstructor(ctc1);


// 为testMy创建一个有一个参数的构造方法
// cp.get("java.lang.String")为一个参数类型为String
CtConstructor ctc2 = new CtConstructor(new CtClass[]{cp.get("java.lang.String")}, cc);
ctc2.setBody("{System.out.println(222);$0.name = $1;}");
cc.addConstructor(ctc2);

// 为testMy创建一个公共方法
// CtClass.voidType为没有参数,printName为方法名
CtMethod ctm = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
// 可见性为公共
ctm.setModifiers(Modifier.PUBLIC);
// 联系下面的代码,这句会报错
//ctm.setModifiers(Modifier.STATIC);
ctm.setBody("{System.out.println(name);}");
cc.addMethod(ctm);

// 设置name的getter和setter
cc.addMethod(CtNewMethod.setter("setName", ccName));
cc.addMethod(CtNewMethod.getter("getName", ccName));

// 将这个类写入当前目录下
cc.writeFile("./");
}
}

反编译之后的testMy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package JavassistTest;

public class TestMy {
private String name = "test";

public TestMy() {
System.out.println(111);
this.name = "kksk";
}

public TestMy(String var1) {
System.out.println(222);
this.name = var1;
}

public void printName() {
System.out.println(this.name);
}

public void setName(String var1) {
this.name = var1;
}

public String getName() {
return this.name;
}
}

Javassist在设置了大量CtClass时会消耗大量内存,API的解决方法是,适时调用CtClassdetach方法释放内存

反射调用

除了写为文件以外,可以通过CtClasstoClass方法将其转换为Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javassist.*;

public class JavasTest {
public static void main(String[] args) throws Exception {
// 和上面代码相同
Object test = cc.toClass().newInstance();
test.getClass().getMethod("printName").invoke(test);
}
}

/*
output:
111
kksk
*/

动态代理

使用一个javassist来新建一个类的操作其实并不常见,真正常见的是利用其进行动态代理。

在之前的JDK动态代理中,我们有一个问题就是其只能同过接口来进行动态代理,实体类就没有办法用这种方法代理了。

Comments