利用Java反射机制和Javassist实现Java对象及其属性的动态创建生成
在开发过程中经常会遇到java对象的属性特征不确定的情况,比如属性的名称,属性的类型,属性的取值等不确定的情况,如何在java运行时获取这些信息?动态的设置java对象的属性值?借助java反射机制以及javassist能够轻松解决这些问题。这篇文章旨在抛砖引玉,不足之处请多多指正。
1.简单介绍Java的反射原理
Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。Java程序要能够运行,java虚拟机需要事先加载java类,目前我们的程序在编译期就已经确定哪些java类需要被加载。
Java的反射机制是在编译时并不确定哪个类需要被加载,而是在程序运行时才加载、探知、自审。这样的特点就是反射。
何为自审:通过Java的反射机制能够探知到java类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。
Java的反射原理最典型的应用就是各种java IDE:比如Jcreator,eclipse,idea等,当我们构建出一个对象时,去调用该对象的方法和属性的时候。一按点,IDE工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供我们进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审的过程。
2. Java的反射API(Reflection API)
Class类:要正确使用Java反射机制就得使用java.lang.Class这个类。它是Java反射机制的起源。当一个类被加载以后,Java虚拟机就会自动产生一个Class对象。通过这个Class对象我们就能获得加载到虚拟机当中这个Class对象对应的方法、成员以及构造方法的声明和定义等信息。
反射API用于反应在当前Java虚拟机中的类、接口或者对象信息
功能:(Object object = new Object(),下面以对象object进行说明)
1) 获取类的Class对象
Ø 如果在运行时一个类的实例已经得到,你可以使用
Class c = 对象名.getClass();例: Class c = object.getClass();Class s = object.getSuperclass();
Ø 如果你在编译期知道类的名字,你可以使用如下的方法Class c =java. awt. Button.class; 或者Class c = Integer.TYPE;
Ø 如果类名在编译期不知道, 但是在运行期可以获得, 你可以使用下面的方法Class c = Class.forName(“类的全路径”);
2) 获取类的Fields ,对Field进行赋值
Ø Field[] fus = object.getClass().getDeclaredField();
Ø Field fu = object.getClass().getDeclaredField(fieldName);//获取对象object的名称为fieldName的属性域。
Ø fu.setAccessible(true) ;//设置属性域的访问属性
Ø fu.set(object,val); //设置object对象的属性值
3) 获取类的Method
Ø Method[] ms= object.getClass().getDeclaredMethods()
4) 获取类的Constructor
5) 新建类的实例
(A)通过Class
(B)通过Constructor对象的方法newInstance.
利用Java反射机制我们可以很灵活的对已经加载到Java虚拟机当中的类信息进行检测。当然这种检测在对运行的性能上会有些减弱,所以什么时候使用反射,就要靠业务的需求、大小,以及经验的积累来决定。
3. 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 的实例表示。这些类定义了修改由它们所表示的对象的所有方法的方法,包括方法或者构造函数中的实际字节码内容。
4. 实例部分
4.1实验准备: Javassist jar包的maven依赖:
4.2实例源码
DynamicCreateObject:
public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
DynamicCreateObject dco = new DaniymicCreateObject();
Object student1 = null, team = null;
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
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
*/
public Object addField(String className,Map
throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {
ClassPool pool = ClassPool.getDefault();//获取javassist类池
CtClass ctClass = pool.makeClass(className,pool.get(DObject.class.getName()));//创建javassist类
// 为创建的类ctClass添加属性
Iterator it = fieldMap.entrySet().iterator();
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类
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;
}
至此,我们动态创建java类,并为java类添动态加属性就完成了。反射机制是框架技术的原理和核心部分。通过反射机制我们可以动态的通过改变配置文件(以后是XML文件)的方式来加载类、调用类方法,以及使用类属性。这样的话,对于编码和维护带来相当大的便利。在程序进行改动的时候,也只会改动相应的功能就行了,调用的方法是不用改的。更不会一改就改全身。