Javassist进行方法插桩
javassist官网 http://jboss-javassist.github.io/javassist/
javassist API网 http://jboss-javassist.github.io/javassist/html/index.html
javassist参考博客 https://www.ibm.com/developerworks/cn/java/j-dyn0916/
Ⅰ插桩
自动用例生成(使用Randoop)
评价(对用例筛选冗余)>功能覆盖、语句覆盖(一般用后者)
>插桩 (插入语句)
用Javassist实现自动插入语句,即插桩
方法层次的插桩:
在方法前插入进入方法的语句,方法后插入退出方法的语句
Ⅱ用Javassist对方法插桩
1‘ 首先在官网下Javassist的jar包,放置在web项目的lib下,在eclipse里右击项目选择build path的最后一个,导入jar包。
2’ 新建一个相对要插桩类a.java的被插桩程序aJssistTiming.java
弄明白这个对方法的插桩,还是得看程序,我对测试第二个博客里的Triangle.java代码进行方法插桩,在方法前后都插入一个输出语句,TriangleJssistTiming.java代码示例如下:
1 import java.io.IOException;
2 import javassist.*;
3
4 public class TriangleJssistTiming {
5 public static void main(String argv[]){
6 try {
7 String classname="Triangle";
8 //获取class文件
9 CtClass clas =ClassPool.getDefault().get(classname);
10
11 if(clas==null){
12 System.out.println("classname"+clas+" not found.");
13 }else{
14 //调用方法
15 addTiming(clas,"isTriangle");
16 addTiming(clas,"getType");
17 addTiming(clas,"diffOfBorders");
18 addTiming(clas,"getBorders");
19 clas.writeFile();
20 }
21 } catch (CannotCompileException ex) {
22 ex.printStackTrace();
23 } catch (NotFoundException ex) {
24 ex.printStackTrace();
25 } catch (IOException ex) {
26 ex.printStackTrace();
27 }
28
29
30 }
31
32 private static void addTiming(CtClass cct,String method) throws NotFoundException, CannotCompileException{
33 //获取方法信息,如果方法不存在,则抛出异常
34 CtMethod ctMethod = cct.getDeclaredMethod(method);
35 //将旧的方法名称进行重新命名
36 String nname = method + "$impl";
37 ctMethod.setName(nname);
38 //方法的副本,采用过滤器的方式
39 CtMethod newCtMethod = CtNewMethod.copy(ctMethod, method, cct, null);
40
41 //为该方法添加时间过滤器来计算时间,并判断获取时间的方法是否有返回值
42 String type = ctMethod.getReturnType().getName();
43 StringBuffer body = new StringBuffer();
44 body.append("{\n long start = System.currentTimeMillis();\n");
45
46 body.append("System.out.println(\" "+ nname +"_in_11\");");
47 //返回值类型不同
48 if(!"void".equals(type)) {
49 body.append(type + " result = ");
50 }
51 //可以通过$$将传递给拦截器的参数,传递给原来的方法
52 body.append(nname + "($$);\n");
53 //输出方法运行时间差
54 // body.append("System.out.println(\"Call to method " + nname + " took \" + \n (System.currentTimeMillis()-start) + " + "\" ms.\");\n");
55 body.append("System.out.println(\" "+ nname +"_out_11\");");
56 if(!"void".equals(type)) {
57 body.append("return result;\n");
58 }
59
60 body.append("}");
61 //替换拦截器方法的主体内容,并将该方法添加到class之中
62 newCtMethod.setBody(body.toString());
63 cct.addMethod(newCtMethod);
64
65 //输出拦截器的代码块
66 System.out.println("拦截器方法的主体:");
67 System.out.println(body.toString());
68 }
69 }
▲1' body.append()插入字符串,里面若是输出语句需要注意用转义字符 “ “ 改成/“ /"
2’ 构造一个addtiming(0方法,方法里构造拦截器时使用一个 java.lang.StringBuffer
来累积正文文本(这显示了处理 String
的构造的正确方法。这种变化取决于原来的方法是否有返回值。如果它 有返回值,那么构造的代码就将这个值保存在局部变量中,这样在拦截器方法结束时就可以返回它。如果原来的方法类型为 void
,那么就什么也不需要保存,也不用在拦截器方法中返回任何内容。
3‘ body.append(nname + "($$);\n")
中 nname
是原来方法修改后的名字。在调用中使用的 $$
标识符是 Javassist 表示正在构造的方法的一系列参数的方式。通过在对原来方法的调用中使用这个标识符,在调用拦截器方法时提供的参数就可以传递给原来的方法。
3‘ 运行
在eclipse里运行插桩程序和被插桩程序,然后看当地目录下又生成一个Triangle.class文件,大小比原来的大一点。
在cmd代码目录下运行该文件 java Triangle
▲原Triangle里没有main方法,需自己插入main方法并创建一个对象引用第一个方法isTriangle()
比较插桩前后Triangle.class,在当地目录找到Triangle.class(后生成的,不在bin目录里,较大的文件),然后用,导入该文件,可以查看原java代码,会发现多了四个类似的方法:
1 import java.io.PrintStream;
2
3 public class Triangle
4 {
5 public static void main(String[] args)
6 {
7 Triangle tr = new Triangle(2L, 3L, 4L);
8 tr.isTriangle(tr);
9 }
10
11 protected long lborderA = 0L;
12 protected long lborderB = 0L;
13 protected long lborderC = 0L;
14
15 public Triangle(long lborderA, long lborderB, long lborderC)
16 {
17 this.lborderA = lborderA;
18
19 this.lborderB = lborderB;
20
21 this.lborderC = lborderC;
22 }
23
24 public boolean isTriangle$impl(Triangle triangle)
25 {
26 boolean isTriangle = false;
27 if ((triangle.lborderA > 0L) && (triangle.lborderA <= 9223372036854775807L) &&
28 (triangle.lborderB > 0L) && (triangle.lborderB <= 9223372036854775807L) &&
29 (triangle.lborderC > 0L) && (triangle.lborderC <= 9223372036854775807L)) {
30 if ((diffOfBorders(triangle.lborderA, triangle.lborderB) < triangle.lborderC) &&
31 (diffOfBorders(triangle.lborderB, triangle.lborderC) < triangle.lborderA) &&
32 (diffOfBorders(triangle.lborderC, triangle.lborderA) < triangle.lborderB)) {
33 isTriangle = true;
34 }
35 }
36 return isTriangle;
37 }
38
39 public String getType$impl(Triangle triangle)
40 {
41 String strType = "Illegal";
42 if (isTriangle(triangle)) {
43 if ((triangle.lborderA == triangle.lborderB) &&
44 (triangle.lborderB == triangle.lborderC)) {
45 strType = "Regular";
46 } else if ((triangle.lborderA != triangle.lborderB) &&
47 (triangle.lborderB != triangle.lborderC) &&
48 (triangle.lborderA != triangle.lborderC)) {
49 strType = "Scalene";
50 } else {
51 strType = "Isosceles";
52 }
53 }
54 return strType;
55 }
56
57 public long diffOfBorders$impl(long a, long b)
58 {
59 return a > b ? a - b : b - a;
60 }
61
62 public long[] getBorders$impl()
63 {
64 long[] borders = new long[3];
65 borders[0] = this.lborderA;
66 borders[1] = this.lborderB;
67 borders[2] = this.lborderC;
68 return borders;
69 }
70
71 public boolean isTriangle(Triangle paramTriangle)
72 {
73 long l = System.currentTimeMillis();
74 System.out.println(" isTriangle$impl_in_11");
75 boolean bool = isTriangle$impl(paramTriangle);
76 System.out.println(" isTriangle$impl_out_11");
77 return bool;
78 }
79
80 public String getType(Triangle paramTriangle)
81 {
82 long l = System.currentTimeMillis();
83 System.out.println(" getType$impl_in_11");
84 String str = getType$impl(paramTriangle);
85 System.out.println(" getType$impl_out_11");
86 return str;
87 }
88
89 public long diffOfBorders(long paramLong1, long paramLong2)
90 {
91 long l1 = System.currentTimeMillis();
92 System.out.println(" diffOfBorders$impl_in_11");
93 long l2 = diffOfBorders$impl(paramLong1, paramLong2);
94 System.out.println(" diffOfBorders$impl_out_11");
95 return l2;
96 }
97
98 public long[] getBorders()
99 {
100 long l = System.currentTimeMillis();
101 System.out.println(" getBorders$impl_in_11");
102 long[] arrayOfLong = getBorders$impl();
103 System.out.println(" getBorders$impl_out_11");
104 return arrayOfLong;
105 }
106 }
总结:
Javassist 不仅是一个处理字节码的库,而且更因为它的另一项功能使得它成为试验 classworking 的很好的起点。这一项功能就是:可以用 Javassist 改变 Java 类的字节码,而无需真正了解关于字节码或者 Java 虚拟机(Java virtual machine JVM)结构的任何内容。
用Javassist API的方法,对插桩程序用ctclass、classpool、CtMethod等方法进行方法插桩,然后生成第二个插桩程序的class文件,此时run这个class文件就会显示插入的语句要输出的内容。