1.位元組碼操作
- JAVA動態性的兩種常見實現方式
位元組碼操作
反射
- 執行時操作位元組碼可以讓我們實現如下功能
動態生成新的類
動態改變某個類的結構(新增/刪除/修改 新的屬性/方法)
- 優勢
比反射開銷小,效能高
JAVAasist效能高於反射,低於asm
2.常見的位元組碼操作類庫
- BCEL
Byte Code Engineering Library (BCEL), 這是Apache Software Foundation 的 Jakarta 專案的一部分.BCEL是Java classworking廣泛使用的一種框,它可以讓您深入JVM組合語言進行類操作的細節.BCEL與Javassist有不同的處理位元組碼方法,BCEL在實際的JVM指令層次上進行操作(BCEI擁有豐富的JVM指令級支援)而Javassist所強調的是原始碼級別的工作
- ASM
是一個輕量級ava位元組碼操作框架,直接涉及量到VM底層的操作和指令
- CGLIB(Code Generation Library)
是一個強大的,高效能,高質量的Code生成類庫,基於ASM實現
- Javassist
是一個開源的分析、編輯和建立Jaw位元組碼的類庫.效能較ASM差,跟cglib差不多,但是使用簡單.很多開源框架都在使用它
3.JAVAssist庫
- 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 at runtime and to modify a class file when the JVM loads it.
-
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 of the specifications of the Java bytecode.The whole API is designed with only the vocabulary of the java language.You can even specify inserted bytecode-level API allows the users to directly edit a class file as other editors.
- Aspect Oriented Programming(AOP面向切面程式設計):Javassist can be a good tool for adding new methods into a class and for inserting before/after/around advice at the both caller and callee sides.
- Reflection:Ones of applications of Javassist is runtime reflection;Javassist enables Java programs to use a metaobject that controls method calls on base-level objects.No specialized complier or virtual machine are needed.
4.JAVAssist庫的API簡介
- javaassist的最外層的API和JAVA的反射包中的API頗為類似
- 它主要由CtClass,CtMethod,以及CtField幾個類組成.用以執行和JDK反射API中java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method.Field相同的操作(Ct為Complie Time)
5.JAVAssist庫的簡單使用
- 建立一個全新的類
- 使用XJAD反編譯工具,將生成的class檔案反編譯成JAVA檔案
使用前先匯入javassist的jar包
Demo:
/**
* 使用javassist生成一個新的類
* @author Matrix42
*
*/
public class Demo01 {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("com.lorinda.bean.Emp");
//建立屬性
CtField f1 = CtField.make("private int empno;", cc);
CtField f2 = CtField.make("private String ename;", cc);
cc.addField(f1);
cc.addField(f2);
//建立方法
CtMethod m1 = CtMethod.make("public int getEmpno(){return empno;}", cc);
CtMethod m2 = CtMethod.make("public void setEmpno(int empno){this.empno = empno;}", cc);
cc.addMethod(m1);
cc.addMethod(m2);
CtMethod m3 = CtMethod.make("public String getEname(){return ename;}", cc);
CtMethod m4 = CtMethod.make("public void setEname(String empno){this.ename = ename;}", cc);
cc.addMethod(m3);
cc.addMethod(m4);
//新增構造器
CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType,pool.get("java.lang.String")}, cc);
constructor.setBody("{this.empno=$1;this.ename=$2;}");
cc.addConstructor(constructor);
//將上面構造好的類寫入到d:/myjava
cc.writeFile("d:/myjava");
System.out.println("生成類,成功!");
}
}
建立完成後使用XJAD反編譯就可以看到原始碼了
反編譯原始碼:
package com.lorinda.bean;
public class Emp
{
private int empno;
private String ename;
public int getEmpno()
{
return empno;
}
public void setEmpno(int i)
{
empno = i;
}
public String getEname()
{
return ename;
}
public void setEname(String s)
{
ename = ename;
}
public Emp(int i, String s)
{
empno = i;
ename = s;
}
}
6.JAVAssist庫的API詳解
-
方法操作
修改已有方法的方法體(插入程式碼到已有方法體)
新增方法
刪除方法
a b c $0,$1,$2,… this and actual parameters $0 代表的是 this,$1 代表方法引數的第一個引數,$2 代表方法引數的第二個引數, 以此類推,$N 代表方法引數的第 N 個引數 $args An arrar of parameters The type of $args is Object[], $args[0] 對應的是 $1 而不是 $0 $$ 所有方法引數的簡寫, 主要用在方法呼叫上 move(String a,String b) move($$) 相當於 move($1,$2) fallthrough path 在類路徑, 原始檔路徑等中有不存在的路徑警告 $cflow $r $_ addCatch() $class $sig
- 屬性操作
修改已有方法的方法體(插入程式碼到已有方法體)
新增方法
刪除方法
Demo:
import java.awt.color.CMMException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
public class Demo02 {
public static void test01() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("javassist.Emp");
byte[] bytes = cc.toBytecode();
System.out.println(Arrays.toString(bytes));
System.out.println(cc.getName()); //獲得類名
System.out.println(cc.getSimpleName()); //獲得簡要類名
System.out.println(cc.getSuperclass()); //獲得父類
System.out.println(cc.getInterfaces()); //獲得介面
}
public static void test02()throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("javassist.Emp");
//CtMethod m = CtMethod.make("public int add(int a,int b){return a+b;}", cc);
CtMethod m = new CtMethod(CtClass.intType,"add",
new CtClass[]{CtClass.intType,CtClass.intType},cc);
m.setModifiers(Modifier.PUBLIC);
m.setBody("{System.out.println("Ha Ha");return $1+$2;}");
cc.addMethod(m);
//通過反射呼叫新生產的方法
Class<?> clazz = cc.toClass();
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("add", int.class,int.class);
Object result = method.invoke(obj, 200,300);
System.out.println(result);
}
public static void test03()throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("javassist.Emp");
CtMethod cm = cc.getDeclaredMethod("sayHello", new CtClass[]{CtClass.intType});
cm.insertBefore("System.out.println($1);");
Class<?> clazz = cc.toClass();
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sayHello", int.class);
method.invoke(obj, 90);
}
public static void test04() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("javassist.Emp");
//CtField f1 = CtField.make("private int empno", cc);
CtField f1 = new CtField(CtClass.intType,"salary",cc);
f1.setModifiers(Modifier.PRIVATE);
cc.addField(f1,"1000");//1000位預設值
// cc.getDeclaredField("ename"); //獲取指定屬性
cc.addMethod(CtNewMethod.getter("salary",f1));
cc.addMethod(CtNewMethod.setter("salary", f1));
}
public static void test05()throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("javassist.Emp");
CtConstructor[] cs = cc.getConstructors();
for (CtConstructor ctConstructor : cs) {
System.out.println(ctConstructor.getLongName());
}
}
public static void main(String[] args) throws Exception {
//test01();
//test02();
// test03();
// test04();
test05();
}
}
- 構造方法操作
getConstructors()
- 註解操作
程式碼片段:
public @interface Author {
String name();
int year();
}
@Author(name="Chiba",year=2005)
public class Point{
int x,y;
}
CtClass cc = ClassPool.getDefault.get("Point");
Object[] all = cc.getAnnotations();
Author a = (Author)all[0];
String name = a.name();
int year = a.year();
System.out.println("name:"+name+",year:"+year);
當呼叫了writeFile(),toClass(),toBytecode(),Javassist會把那個CtClass物件凍結,如果想使用凍結的物件可以呼叫.defrose()方法
-
侷限性
JDK5.0新語法不支援(包括泛型,列舉),不支援註解修改,單可以的通過底層javasist類來解決,具體參考:javassist.bytecode.annotation
不支援陣列的初始化,如String[]{“1″,”2”},除非只有陣列容量為1
不支援內部類盒匿名類
不支援continue盒break表示式
對於繼承關係,有些語法不支援,如:
- class A{}
- class B extends A{}
- class C extends B{}