Javassist详解

来源:互联网 发布:mac队员 编辑:程序博客网 时间:2024/06/06 08:56

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 style="font-size:14px;">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() */@Overridepublic String toString() {return "Person [name=" + name + ", address=" + address + "]";}}</span>

b. 自定义一个ClassLoader

<span style="font-size:14px;">/** * <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 style="font-size:14px;">/** * <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>


由于JVM通过字节码的二进制信息加载类的,那么如果我们在程序运行期,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成相应的类,这样,就完成了在代码中,动态创建一个类的能力了。



在运行期间可以按照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 style="font-size:14px;">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>();// 属性-取值mapfieldMap.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文件。



0 0
原创粉丝点击