Markdown版本笔记 | 个人GitHub首页 | 个人博客 | 个人微信 | 个人邮箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
官网
GitHub
WIKI -很是详细java
Java bytecode engineering toolkit since 1999git
Javassist是一个开源的分析、编辑和建立Java字节码的类库
。是由日本的 Shigeru Chiba 所建立的,它已加入了开放源代码 JBoss 应用服务器项目,经过使用 Javassist 对字节码操做为 JBoss 实现动态"AOP"框架。github
关于java字节码的处理,目前有不少工具,如bcel,ASM
。不过这些都须要直接跟虚拟机指令
打交道。若是你不想了解虚拟机指令,能够采用javassist。javassist是jboss的一个子项目,其主要的优势在于简单、快速。直接使用java编码的形式,而不须要了解虚拟机指令,就能动态改变类的结构,或者动态生成类
。服务器
官方简介
Javassist(Java Programming Assistant)使Java字节码
操做变得简单。它是一个用于在Java中编辑字节码的类库;它使Java程序可以在运行时
定义新类,并在JVM加载时修改类文件。微信
Javassist (Java Programming Assistant) makes Java
bytecode
manipulation simple. It is a class library for editing bytecodes in Java; it enables Java programs to define a new class atruntime
and to modify a class file when the JVM loads it. 框架
与其余相似的字节码编辑器不一样,Javassist提供两个级别的API:源级别和字节码级别
。若是用户使用源级API,他们能够在不了解Java字节码规范
的状况下编辑class文件。整个API仅使用Java语言的词汇表进行设计。您甚至能够以源文本的形式指定插入的字节码; Javassist即时
编译它。另外一方面,字节码级API容许用户直接
编辑class文件做为其余编辑器。编辑器
Unlike other similar bytecode editors, Javassist provides two levels of API:
source level and bytecode level
. If the users use the source- level API, they can edit a class file without knowledge ofthe specifications of the Java bytecode
. The whole API is designed with only the vocabulary of the Java language. You can even specify inserted bytecode in the form of source text; Javassist compiles iton the fly
. On the other hand, the bytecode-level API allows the users todirectly
edit a class file as other editors.ide
PS:这个框架在Android中可用,目前还没发现兼容性问题!工具
package com.bqt.test; public class Person { public void hello(String s) { System.out.println(s); } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); CtMethod cm = cc.getDeclaredMethod("hello", new CtClass[] { pool.get("java.lang.String") }); cm.setBody("{" + "System.out.println(\"你好:\" + $1);" + "}"); cc.writeFile("d:/test");//保存到指定目录 cc.toClass(); //加载修改后的类,注意:必须保证调用前此类未加载 new Person().hello("包青天");
运行结果为:测试
你好:包青天
注意,若是使用 JDK9 如下的JDK ,同时使用 3.20.0-GA 以上版本的 Javassist,调用 toClass 方法会报 StackWalker 异常
生成的类经反编译后的源码为:
package com.bqt.test; import java.io.PrintStream; public class Person { public void hello(String paramString) { System.out.println("你好" + paramString); } }
同个 Class 是不能在同个 ClassLoader 中加载两次的,因此在输出 CtClass 的时候须要注意下。
例如上例中,若是在调用toClass()
前 Person 类已经加载过了,则直接报异常:
new Person().hello("包青天"); ClassPool.getDefault().get("com.bqt.test.Person").toClass();
Exception in thread "main" javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "com/bqt/test/Person" at javassist.ClassPool.toClass(ClassPool.java:1170) at javassist.ClassPool.toClass(ClassPool.java:1113) at javassist.ClassPool.toClass(ClassPool.java:1071) at javassist.CtClass.toClass(CtClass.java:1264) at com.bqt.test.Main.main(Main.java:9) Caused by: java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "com/bqt/test/Person" at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(Unknown Source) at java.lang.ClassLoader.defineClass(Unknown Source) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at javassist.ClassPool.toClass2(ClassPool.java:1183) at javassist.ClassPool.toClass(ClassPool.java:1164) ... 4 more
解决方法:指定一个未加载的ClassLoader
为了方便,Javassist 也提供一个 Classloader 供使用,例如
new Person().hello("包青天"); ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); CtMethod cm = cc.getDeclaredMethod("hello", new CtClass[] { pool.get("java.lang.String") }); cm.setBody("{" + "System.out.println(\"你好鸭:\" + $1);" + "}"); cc.writeFile("d:/test");//保存到指定目录 Translator translator = new Translator() { @Override public void start(ClassPool classPool) throws NotFoundException, CannotCompileException { System.out.println("start"); } @Override public void onLoad(ClassPool classPool, String paramString) throws NotFoundException, CannotCompileException { System.out.println("onLoad:" + paramString); //com.bqt.test.Person new Person().hello("白乾涛"); //调用的是原始类的方法 } }; Loader classLoader = new Loader(pool); //Javassist 提供的 Classloader classLoader.addTranslator(pool, translator); //监听 ClassLoader 的生命周期 Class clazz = classLoader.loadClass("com.bqt.test.Person"); new Person().hello("白乾涛2"); //调用的是原始类的方法 clazz.getDeclaredMethod("hello", String.class).invoke(clazz.newInstance(), "你妹"); //调用的是新类的方法 Class clazz2 = Class.forName("com.bqt.test.Person"); clazz2.getDeclaredMethod("hello", String.class).invoke(clazz2.newInstance(), "你妹2"); //调用原始类的方法
打印日志:
包青天 start onLoad:com.bqt.test.Person 白乾涛 白乾涛2 你好鸭:你妹 你妹2
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); byte[] bytes = cc.toBytecode();//获得字节码 System.out.println(bytes.length); System.out.println(cc.getName());//获取类名 System.out.println(cc.getSimpleName());//获取简要类名 System.out.println(cc.getSuperclass().getName());//获取父类 System.out.println(Arrays.toString(cc.getInterfaces()));//获取接口 for (CtConstructor con : cc.getConstructors()) {//获取构造方法 System.out.println("构造方法 "+con.getLongName()); } for (CtMethod method : cc.getMethods()) {//获取方法 System.out.println(method.getLongName()); }
打印内容:
562 com.bqt.test.Person Person java.lang.Object [] 构造方法 com.bqt.test.Person() java.lang.Object.equals(java.lang.Object) java.lang.Object.finalize() com.bqt.test.Person.hello2(java.lang.String) java.lang.Object.toString() java.lang.Object.getClass() java.lang.Object.notifyAll() java.lang.Object.hashCode() java.lang.Object.wait() java.lang.Object.notify() com.bqt.test.Person.hello(java.lang.String) java.lang.Object.wait(long) java.lang.Object.wait(long,int) java.lang.Object.clone()
获取注解信息
Object[] annotations = cf.getAnnotations(); //获取类、方法、字段等上面定义的注解信息 SerializedName annotation = (SerializedName) annotations[0]; //遍历判断注解类型 System.out.println(annotation.value()); //获取注解的值
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("com.bqt.test.User"); //建立属性 cc.addField(CtField.make("private int id;", cc)); cc.addField(CtField.make("private String name;", cc)); //建立方法 cc.addMethod(CtMethod.make("public String getName(){return name;}", cc)); cc.addMethod(CtMethod.make("public void setName(String name){this.name = name;}", cc)); //添加构造器 CtConstructor constructor = new CtConstructor(new CtClass[] { CtClass.intType, pool.get("java.lang.String") }, cc); constructor.setBody("{this.id=id;this.name=name;}"); cc.addConstructor(constructor); cc.writeFile("d:/test");
生成的类经反编译后的源码为:
package com.bqt.test; public class User { private int id; private String name; public User(int paramInt, String paramString) { this.id = this.id; this.name = this.name; } public String getName() { return this.name; } public void setName(String paramString) { this.name = paramString; } }
package com.bqt.test; public class Person { public int hello(String s) { return s.length(); } public String hello2(String s) { return s; } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); //方式一 CtMethod cm1 = CtMethod.make("public int add1(int a, String b){return a+b.length();}", cc);//第一种方式,完整的方法以字符串形式传递过去 cc.addMethod(cm1); //cc.removeMethod(cm3) 删除一个方法 //方式二 CtClass[] parameters = new CtClass[] { CtClass.intType, pool.get("java.lang.String") }; CtMethod cm2 = new CtMethod(CtClass.intType, "add2", parameters, cc);//第二种方式,返回值类型,方法名,参数,对象 cm2.setModifiers(Modifier.PUBLIC);//访问范围 cm2.setBody("{return $1+$2.length();}");//方法体 cc.addMethod(cm2); cc.writeFile("D:/test");//保存到指定位置
生成的类经反编译后的源码为:
package com.bqt.test; public class Person { public int hello(String s) { return s.length(); } public String hello2(String s) { return s; } public int add1(int paramInt, String paramString) { return paramInt + paramString.length(); } public int add2(int paramInt, String paramString) { return paramInt + paramString.length(); } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); //方式一 CtField cf = new CtField(CtClass.intType, "age", cc); cf.setModifiers(Modifier.PRIVATE); cc.addField(cf); cc.addMethod(CtNewMethod.getter("getAge", cf)); cc.addMethod(CtNewMethod.setter("setAge", cf)); //方式二 CtField cf2 = CtField.make("private String name;", cc); cc.addField(cf2); cc.addMethod(CtNewMethod.getter("getName", cf2)); //快捷的添加get/set方法 cc.addMethod(CtNewMethod.setter("setName", cf2)); cc.writeFile("D:/test");//保存到指定位置
生成的类经反编译后的源码为:
package com.bqt.test; public class Person { private int age; private String name; public int hello(String s) { return s.length(); } public String hello2(String s) { return s; } public int getAge() { return this.age; } public void setAge(int paramInt) { this.age = paramInt; } public String getName() { return this.name; } public void setName(String paramString) { this.name = paramString; } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); //方式一 CtMethod cm = cc.getDeclaredMethod("hello", new CtClass[] { pool.get("java.lang.String") }); cm.insertBefore("System.out.println(\"调用前2\");");//调用前 cm.insertBefore("System.out.println(\"调用前1\");"); cm.insertAt(20094, "System.out.println(\"在指定行插入代码\");");//貌似行号胡乱写也能够 cm.insertAfter("System.out.println(\"调用后1\");");//调用后 cm.insertAfter("System.out.println(\"调用后2\");");//调用后 //方式二 CtMethod cm2 = cc.getDeclaredMethod("hello2", new CtClass[] { pool.get("java.lang.String") }); cm2.setBody("{" + // 你只须要正常写代码逻辑就能够了,复制过来时,一些IDE,好比AS会自动帮你添加转义字符 "if ($1 == null) {\n" + //$0表明的是this,$1表明方法参数的第一个参数、$2表明方法参数的第二个参数 "\treturn \"\";\n" + // "}\n" + // "return \"你好:\" + $1;" + // "}"); cc.writeFile("D:/test");//保存到指定位置
生成的类经反编译后的源码为:
package com.bqt.test; import java.io.PrintStream; public class Person { public int hello(String s) { System.out.println("调用前1"); System.out.println("调用前2"); System.out.println("在指定行插入代码"); int i = s.length(); System.out.println("调用后1"); int j = i; System.out.println("调用后2"); return j; } public String hello2(String paramString) { if (paramString == null) { return ""; } return "你好:" + paramString; } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); CtField cf = cc.getField("age"); FieldInfo fInfo = cf.getFieldInfo(); ConstPool cp = cc.getClassFile().getConstPool(); //修改注解的值 Annotation annotation = new Annotation(SerializedName.class.getName(), cp);//获取注解 annotation.addMemberValue("value", new StringMemberValue("_AGE", cp));//设置注解指定字段的值 AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);//获取注解信息 annotationsAttribute.setAnnotation(annotation);//设置注解 fInfo.addAttribute(annotationsAttribute);//添加(覆盖)注解信息 cc.writeFile("D:/test");//保存到指定位置
修改前
class Person { @SerializedName("AGE") public int age = 28; }
修改后
class Person { @SerializedName("_AGE") public int age = 28; }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); ProxyFactory factory = new ProxyFactory();//代理类工厂 factory.setSuperclass(cc.toClass());//设置父类,ProxyFactory将会动态生成一个类,继承该父类 //设置过滤器,判断哪些方法调用须要被拦截 factory.setFilter(new MethodFilter() { @Override public boolean isHandled(Method m) { return m.getName().startsWith("hello"); } }); Class<?> clazz = factory.createClass();//建立代理类型 Person proxy = (Person) clazz.newInstance();//建立代理实例 //设置代理处理方法 ((ProxyObject) proxy).setHandler(new MethodHandler() { @Override public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { System.out.println(thisMethod.getName() + "被调用了"); try { Object ret = proceed.invoke(self, args);//thisMethod为被代理方法,proceed为代理方法,self为代理实例,args为方法参数 System.out.println("返回值: " + ret); return ret; } finally { System.out.println(thisMethod.getName() + "调用完毕"); } } }); //测试 proxy.hello("包青天"); System.out.println(proxy.getClass().getName()); //com.bqt.test.Person_$$_jvstee7_0
打印日志:
hello被调用了 返回值: 3 hello调用完毕 com.bqt.test.Person_$$_jvstee7_0
2019-1-7