java的字节码工具-javassist体验二

上次说了可以改写类,那进一步的我们可以做一下类似于之前提过的通过字节码来做切面的工作
首先我们有一个很简单的类和方法

1
2
3
4
5
6
7
8
public class DemoService {
public List<String> queryList(String name, String title, String n3) {
List<String> list = new ArrayList<>();
list.add(name);
list.add(title);
return list;
}
}

就是一个方法,输入几个字符串,然后加到list里,那么比如我们想知道打印入参和返回结果
这里比较类似于我们在用aop的时候就是织入代码
正好javassist有这样的操作接口
就是我们可以找到方法,然后使用

1
javassist.CtBehavior#insertBefore(java.lang.String)

1
javassist.CtBehavior#insertAfter(java.lang.String)

就可以在我们找到类和方法以后

1
2
3
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(targetClass);
CtMethod method = cc.getDeclaredMethod(targetMethod);

对方法的前后进行逻辑织入

1
2
3
4
5
6
7
8
9
10
11
12
method.insertBefore(
"{ java.lang.StringBuilder _paramValues = new StringBuilder();" +
" Object[] params = $args;" +
" if(params != null) {" +
" for(int i=0; i<params.length; i++) {" +
" if(i > 0) _paramValues.append(\", \");" +
" _paramValues.append(String.valueOf(params[i]));" +
" }" +
" }" +
" System.out.println(\"[Method Entry] " + targetMethod + " params: \" + _paramValues.toString());" +
"}"
);

这里我们使用了直接织入代码的形式,在javassist中可以用 $args 来获取参数,然后循环参数, 把每个参数拼接以后作为字符串输出
然后我们想要在函数返回前获取返回值,这个也是有个语法糖 $_

1
2
3
4
5
6
7
StringBuilder after = new StringBuilder();
after.append("{")
.append(" java.util.List _result = $_;\n")
.append(" String _resultStr = (_result != null ? _result.toString() : \"null\");\n")
.append(" int _size = (_result != null ? _result.size() : 0);\n")
.append(" System.out.println(\"[Method Exit] " + targetMethod + " returns: \" + _resultStr + \" size: \" + _size);\n")
.append("}");

这里刚好我是list返回的,就直接这么处理了, 我们通过一个简单的调用来验证下

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
public static void main(String[] args) throws Exception {
// 假设要监控 org.example.DemoService 类中的 queryList 方法
addMonitoringNew("org.example.DemoService", "queryList");
// 2. 使用自定义类加载器加载增强后的类
ClassLoader classLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.equals("org.example.DemoService")) {
try {
// 读取增强后的类文件
byte[] bytes = java.nio.file.Files.readAllBytes(
java.nio.file.Paths.get("org/example/DemoService.class")
);
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
throw new ClassNotFoundException("Failed to load enhanced class", e);
}
}
return super.loadClass(name);
}
};

// 3. 加载增强后的类
Class<?> enhancedClass = classLoader.loadClass("org.example.DemoService");

// 4. 创建实例
Object instance = enhancedClass.newInstance();

// 5. 获取并调用方法
Method processMethod = enhancedClass.getMethod("queryList", String.class, String.class, String.class); // 假设方法接收一个String参数
Object result = processMethod.invoke(instance, "name1", "title1", "n3");

System.out.println("Method returned: " + result);
}
}
可以看到我们的输出结果
```java
Successfully enhanced method: queryList
[Method Entry] queryList params: name1, title1, n3
[Method Exit] queryList returns: [name1, title1] size: 2
Method returned: [name1, title1]

这里其实核心很依赖两个语法糖,类似这样的还有一些

1
2
3
4
5
6
7
8
9
10
11
12
$0, $1, $2, ...    	this and actual parameters
$args An array of parameters. The type of $args is Object[].
$$ All actual parameters.
For example, m($$) is equivalent to m($1,$2,...)

$cflow(...) cflow variable
$r The result type. It is used in a cast expression.
$w The wrapper type. It is used in a cast expression.
$_ The resulting value
$sig An array of java.lang.Class objects representing the formal parameter types.
$type A java.lang.Class object representing the formal result type.
$class A java.lang.Class object representing the class currently edited.

可以结合文档仔细学习下