Javassist详解,
Javassist详解,
1. 简介
在博客中我们有提到关于Java反射,Java反射可以实现运行时加载,探知,自省,使用编译期完全未知的classes,获悉其完整构造,并生成其实体对象,或对fields设值。
自审:通过Java的反射机制能够探知到java类的基本机构,这种对java类结构探知的能力,我们称为Java类的“自审”。
Java的反射原理最典型的应用就是各种java IDE:比如Jcreateor,eclipse,idea等,当我们构造出一个对象时,去调用该对象的方法和属性的的时候。一按点,IDE工具就会自动的把该对象能够使用的素有的方法和属性全部列出来,供我们进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知,自审的过程。Java反射能够将二进制class文件加载到虚拟机中并接着可以生成类的实例,而class文件的生成则是由java编译器由编译器生成,那么是否存在一种技术,我们可以在程序运行时,可以动态创建类,更改类的属性,添加类的方法,以及动态生成class文件呢,Javassist就会帮我们完成这种功能。
在介绍Javassist之前,我们先来简单介绍下class文件及其加载
2. class文件简介及其加载
Java编译器编译好Java文件之后,产生.class文件在磁盘中,class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,读取二进制数据,加载到内存中,解析.class文件内的信息,生成对应的Class对象。
下面通过一段代码演示手动加载class文件到系统内,转换成class对象,然后再实例化的过程:
a. 先创建一个Person类
<span >public class Person {
private String name;
private String address;
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the address
*/
public String getAddress() {
return address;
}
/**
* @param address the address to set
*/
public void setAddress(String address) {
this.address = address;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Person [name=" + name + ", address=" + address + "]";
}
}</span>
b. 自定义一个ClassLoader
<span >/**
* <pre>
* 项目名: javassist-demo1
* 类名: CustomClassLoader.java
* 类描述:自定义类加载器
* </pre>
*/
public class CustomClassLoader extends ClassLoader {
/**
* @param b
* @param off
* @param len
* @return
*/
@SuppressWarnings("deprecation")
public Class<?> defineCustomClass(byte[] b, int off, int len) {
return super.defineClass(b, off, len);
}
}</span>
c. 测试实例
<span >/**
* <pre>
* 项目名: javassist-demo1
* 类名: TestMain.java
* 类描述:
*/
public class TestMain {
public static void main(String[] args) throws FileNotFoundException, IOException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException{
File file = new File(".");
System.out.println(file.getCanonicalPath());
InputStream input = new FileInputStream(file.getCanonicalPath()+"\\target\\classes\\cn\\test\\Person.class");
byte[] result = new byte[1024];
int count = input.read(result);
CustomClassLoader classLoader = new CustomClassLoader();
Class clazz = classLoader.defineCustomClass(result, 0, count);
System.out.println(clazz.getName());
}
}</span>
在运行期间可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。
3. Java字节码生成开源框架-Javassist
Javassist是一个开源的分析、编辑和创建Java字节码的类库。关于java字节码的处理,目前有很多工具,如bcel,asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。简而言之:Javassist 能够转换现有类的基本内容,或创建一个新类。
Javassist 可以检查、编辑以及创建 Java 二进制类。检查方面基本上与通过 Reflection API 直接在 Java 中进行的一样。Javassist 使用类池 javassist.ClassPool 类跟踪和控制所操作的类。其工作方式与 JVM 类装载器非常相似,但是有一个重要的区别是它不是将装载的、要执行的类作为应用程序的一部分链接,类池使所装载的类可以通过 Javassist API 作为数据使用。可以使用默认的类池,它是从 JVM 搜索路径中装载的,也可以定义一个搜索自定义路径列表的类池。甚至可以直接从字节数组或者流中装载二进制类,以及从头开始创建新类。
装载到类池中的类由 javassist.CtClass 实例表示。与标准的 Java java.lang.Class 类一样, CtClass 提供了检查类数据(如字段和方法)的方法。不过,这只是 CtClass 的部分内容,它还定义了在类中添加新字段、方法和构造函数、以及改变类、父类和接口的方法。奇怪的是,Javassist 没有提供删除一个类中字段、方法或者构造函数的任何方法。字段、方法和构造函数分别由 javassist.CtField、javassist.CtMethod 和 javassist.CtConstructor 的实例表示。这些类定义了修改由它们所表示的对象的所有方法的方法,包括方法或者构造函数中的实际字节码内容。
下面就是用javassist的api在程序运行期创建一个新类,然后实例化:
public class DynamicCreateObject {
<span >
public static void main(String[] args) throws NotFoundException,
CannotCompileException, IllegalAccessException,
InstantiationException, NoSuchMethodException,
InvocationTargetException, ClassNotFoundException, IOException {
DynamicCreateObject dco = new DynamicCreateObject();
Object student1 = null, team = null;
Map<String, Object> fieldMap = new HashMap<String, Object>();// 属性-取值map
fieldMap.put("name", "xiao ming");
fieldMap.put("age", 27);
student1 = dco.addField("Student", fieldMap);// 创建一个名称为Student的类
Class c = Class.forName("Student");
Object s1 = c.newInstance();// 创建Student类的对象
Object s2 = c.newInstance();
dco.setFieldValue(s1, "name", " xiao ming ");// 创建对象s1赋值
dco.setFieldValue(s2, "name", "xiao zhang");
fieldMap.clear();
List<Object> students = new ArrayList<Object>();
students.add(s1);
students.add(s2);
fieldMap.put("students", students);
team = dco.addField("Team", fieldMap);// //创建一个名称为Team的类
Field[] fields = team.getClass().getDeclaredFields();
if (fields != null) {
for (Field field : fields)
System.out.println(field.getName() + "=" + dco.getFieldValue(team, field.getName()));
}
}
/**
*
* 为对象动态增加属性,并同时为属性赋值
* @param className 需要创建的java类的名称
* @param fieldMap 字段-字段值的属性map,需要添加的属性
* @return
* @throws NotFoundException
* @throws CannotCompileException
* @throws IOException
*/
@SuppressWarnings("rawtypes")
public Object addField(String className, Map<String, Object> fieldMap) throws NotFoundException, CannotCompileException, IllegalAccessException,
InstantiationException, IOException {
ClassPool pool = ClassPool.getDefault();// 获取javassist类池
CtClass ctClass = pool.makeClass(className, pool.get(Object.class.getName()));// 创建javassist类
// 为创建的类ctClass添加属性
Iterator it = fieldMap.entrySet().iterator();
StringBuilder sb = new StringBuilder();
while (it.hasNext()) { // 遍历所有的属性
Map.Entry entry = (Map.Entry) it.next();
String fieldName = (String) entry.getKey();
Object fieldValue = entry.getValue();
// 增加属性,这里仅仅是增加属性字段
String fieldType = fieldValue.getClass().getName();
CtField ctField = new CtField(pool.get(fieldType), fieldName, ctClass);
ctField.setModifiers(Modifier.PUBLIC);
ctClass.addField(ctField);
}
Class c = ctClass.toClass();// 为创建的javassist类转换为java类
ctClass.writeFile("E://test");
Object newObject = c.newInstance();// 为创建java对象
// 为创建的类newObject属性赋值
it = fieldMap.entrySet().iterator();
while (it.hasNext()) { // 遍历所有的属性
Map.Entry entry = (Map.Entry) it.next();
String fieldName = (String) entry.getKey();
Object fieldValue = entry.getValue();
// 为属性赋值
this.setFieldValue(newObject, fieldName, fieldValue);
}
return newObject;
}
/**
*
* 获取对象属性赋值
* @param dObject
* @param fieldName 字段别名
* @return
*/
public Object getFieldValue(Object dObject, String fieldName) {
Object result = null;
try {
Field fu = dObject.getClass().getDeclaredField(fieldName); // 获取对象的属性域
try {
fu.setAccessible(true); // 设置对象属性域的访问属性
result = fu.get(dObject); // 获取对象属性域的属性值
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return result;
}
/**
*
* 给对象属性赋值
* @param dObject
* @param fieldName
* @param val
* @return
*/
public Object setFieldValue(Object dObject, String fieldName, Object val) {
Object result = null;
try {
Field fu = dObject.getClass().getDeclaredField(fieldName); // 获取对象的属性域
try {
fu.setAccessible(true); // 设置对象属性域的访问属性
fu.set(dObject, val); // 设置对象属性域的属性值
result = fu.get(dObject); // 获取对象属性域的属性值
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return result;
}
}</span>
程序中我们设置了新的class文件存放路径:E:\Test文件夹下,打开文件夹,发现下面就有两个Student.class文件和Team.class文件。
相关文章
- 暂无相关文章
用户点评