javassist 基本用法
Posted 专注着
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javassist 基本用法相关的知识,希望对你有一定的参考价值。
Javassist是一个能够操作字节码框架,在学习的过程中存在了一些问题,用博客的方式记录下来,希望对大家有所帮助。
一、实例功能
学习的实例来自于 IBM developer 主要功能实现计算一个方式具体的执行时间.
二、代码实例
package org.java.javassist.one;
/**
* 该类并不是对StringBuilder进行解释,而是提供了中方式,方便我们来使用javassist的一些细节
* @author xianglj
* @date 2016/7/13
* @time 9:59
*/
public class StringBuilderTest
/**
* 假如我们现在需要计算该程序的计算时间:
* 则可以标记开始时间(start)和结束时间(end)
* 最终的执行时间为(end - start)的值
* @param length
* @return
*/
public String buildString(int length)
String result = "";
for(int i = 0; i<length; i++ )
result += (i %26 + 'a');
return result;
public static void main(String[] args)
/**
* class.getName()返回的字符串中,不仅包括了类的名称,同时也包含了该类所在的包名称
* <pre>
* 格式:
* packagename.classname
* </pre>
*/
// System.out.println(StringBuilderTest.class.getName());
StringBuilderTest test = new StringBuilderTest();
if(null != args)
for(int i = 0, len = args.length; i<len; i++)
String result = test.buildString(Integer.parseInt(args[i]));
System.out.println("result:" + result);
这是一个基本实例,通过一个参数传入的length,来生成length长度的字符串。(但是该方式存在一个很验证的性能问题,就是当length的长度组件增大时,该方法的效率就会越低),但在此处不必关系方法的效率问题
三、解决方案
1) 第一个中解决方案,也是最直观的方式,就是在进入方法时,记录一个当前时间 start , 当代码执行完成之后,获取当前时间 end , 然后采用 (end - start)的方式,来获取代码执行时间
2) 第二中方式,我们采用Javassit框架来实现:
(1) 使用通过ClassPool 来获取 CtClass对象
(2) 从CtClass对象中,获取buildString()的方法
(3) 为buildString()方法添加代码块
特别说明:
为方法添加代码块,有三种方式可以实现
* <pre>
* 1. 添加的代码不能引用在方法中其他地方定义的局部变量。这种限制使我不能在 Javassist 中使用在源代码中使用的同样方法实现计时代码,
* 在这种情况下,我在开始时添加的代码中定义了一个新的局部变量,并在结束处添加的代码中引用这个变量。
*
* 2. 我 可以在类中添加一个新的成员字段,并使用这个字段而不是局部变量。不过,这是一种糟糕的解决方案,
* 在一般性的使用中有一些限制。例如,考虑在一个递归方法中会发生的事情。每次方法调用自身时,上次保存的开始时间值就会被覆盖并且丢失。
*
* 3. 我可以保持原来方法的代码不变,只改变方法名,然后用原来的方法名增加一个新方法。
* </pre>
前两种方式,在实现上都有一定的问题,所以我们采用第三种方式实现,会比较的容易实现
代码如下:
package org.java.javassist.one;
import javassist.*;
import java.io.IOException;
/**
* 通过Javassist来为需要实现计算的方法前后各加上一个拦截器,
* 依次来实现方法计算的时间
* <pre>
* 1. 添加的代码不能引用在方法中其他地方定义的局部变量。这种限制使我不能在 Javassist 中使用在源代码中使用的同样方法实现计时代码,
* 在这种情况下,我在开始时添加的代码中定义了一个新的局部变量,并在结束处添加的代码中引用这个变量。
*
* 2. 我 可以在类中添加一个新的成员字段,并使用这个字段而不是局部变量。不过,这是一种糟糕的解决方案,
* 在一般性的使用中有一些限制。例如,考虑在一个递归方法中会发生的事情。每次方法调用自身时,上次保存的开始时间值就会被覆盖并且丢失。
*
* 3. 我可以保持原来方法的代码不变,只改变方法名,然后用原来的方法名增加一个新方法。
* </pre>
* @author xianglj
* @date 2016/7/13
* @time 10:07
*/
public class JavassistTiming
public static void main(String[] args)
//开始获取class的文件
javassist();
public static void javassist()
//开始获取class的文件
try
// String classFileName = StringBuilderTest.class.getName();
String classFileName = "org.java.javassist.one.StringBuilderTest";
CtClass ctClass = ClassPool.getDefault().getCtClass(classFileName);
if(ctClass == null)
System.out.println("Class File (" + classFileName + ") Not Found.....");
else
addTiming(ctClass, "buildString");
//为class添加计算时间的过滤器
ctClass.writeFile();
Class<?> clazz = ctClass.toClass();
StringBuilderTest test = (StringBuilderTest) clazz.newInstance();
test.buildString(2000);
catch (NotFoundException e) //类文件没有找到
e.printStackTrace();
catch (CannotCompileException e)
e.printStackTrace();
catch (IOException e)
e.printStackTrace();
catch (InstantiationException e)
e.printStackTrace();
catch (IllegalAccessException e)
e.printStackTrace();
/**
* 为方法添加拦截器:
* <pre>
* 构造拦截器方法的正文时使用一个 java.lang.StringBuffer 来累积正文文本(这显示了处理 String 的构造的正确方法,
* 与在 StringBuilder 的构造中使用的方法是相对的)。这种变化取决于原来的方法是否有返回值。
* 如果它 有返回值,那么构造的代码就将这个值保存在局部变量中,这样在拦截器方法结束时就可以返回它。
* 如果原来的方法类型为 void ,那么就什么也不需要保存,也不用在拦截器方法中返回任何内容。
* </pre>
* @param clazz 方法所属的类
* @param method 方法名称
*/
private static void addTiming(CtClass clazz, String method) throws NotFoundException, CannotCompileException
//获取方法信息,如果方法不存在,则抛出异常
CtMethod ctMethod = clazz.getDeclaredMethod(method);
//将旧的方法名称进行重新命名,并生成一个方法的副本,该副本方法采用了过滤器的方式
String nname = method + "$impl";
ctMethod.setName(nname);
CtMethod newCtMethod = CtNewMethod.copy(ctMethod, method, clazz, null);
/*
* 为该方法添加时间过滤器,用来计算时间。
* 这就需要我们去判断获取时间的方法是否具有返回值
*/
String type = ctMethod.getReturnType().getName();
StringBuffer body = new StringBuffer();
body.append("\\n long start = System.currentTimeMillis();\\n");
if(!"void".equals(type))
body.append(type + " result = ");
//可以通过$$将传递给拦截器的参数,传递给原来的方法
body.append(nname + "($$);\\n");
// finish body text generation with call to print the timing
// information, and return saved value (if not void)
body.append("System.out.println(\\"Call to method " + nname + " took \\" + \\n (System.currentTimeMillis()-start) + " + "\\" ms.\\");\\n");
if(!"void".equals(type))
body.append("return result;\\n");
body.append("");
//替换拦截器方法的主体内容,并将该方法添加到class之中
newCtMethod.setBody(body.toString());
clazz.addMethod(newCtMethod);
//输出拦截器的代码块
System.out.println("拦截器方法的主体:");
System.out.println(body.toString());
可能会出现的问题:
1. LinkageError 我在实践的过程中,由于想方便,采用了StringBuilderTest.class.getName() 的方法来代替手写的字符串,这个时候,我在使用CtClass.toClass()时出现了异常,异常原因大致为: 一个Class只能被加载一次,因为我们在调用toClass()方法时,会去再加载Class,所以会出现重复加载。
官方文档如下 Javassist Tutorial
初步学习就到这里,后面会继续更新关于该框架的学习
以上是关于javassist 基本用法的主要内容,如果未能解决你的问题,请参考以下文章