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对象。

javassist 处理注解 javassist原理_java

下面通过一段代码演示手动加载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()
	 */
	@Override
	public 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文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成相应的类,这样,就完成了在代码中,动态创建一个类的能力了。


javassist 处理注解 javassist原理_字节码_02

在运行期间可以按照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>();// 属性-取值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文件。

javassist 处理注解 javassist原理_java_03