文章是我自己的学习总结,适合有一定Java基础的人看!
动态编译、字节码操作、自定义类加载器、反射
这里记录的是在Java中动态的操作类的信息的集中方法,只记录一下他们的区别:
动态编译
动态编译的作用是将一个Java文件编译成class文件供jvm来使用,是动态加载一个类的第一件事。代码如下:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null, null, null, "f:/myjava/HelloWorld.java");
System.out.println(result==0?"编译成功":"编译失败");
字节码操作
在通过动态编译得到class的文件之后,我们还可以直接对这个class文件进行操作。一般反射可以做的事情在字节码操作中都可以做到,字节码操作不同于反射的地方是字节码操作是直接操作class文件,而反射操作的是jvm堆内存中的一个对象,所以相对于反射,字节码操作的内容更加自由。不仅可以获得类的信息,还可以修改类的信息,甚至我们可以直接新创建一个class文件,向这个class文件中写入我们需要的类的信息。
字节码操作一般使用的是javassist类库,它里面有很多可供操作字节码的方法,下面是一些简单代码,还有其他操作可以去网上查一下
ClassPool pool = ClassPool.getDefault();
CtClass cc=pool.makeClass("other.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);
// 添加构造器
CtConstructor constructor = new CtConstructor(new CtClass[] { CtClass.intType, pool.get("java.lang.String") },cc);
constructor.setBody("{this.empno=empno; this.ename=ename;}");
cc.addConstructor(constructor);
cc.writeFile("f:/myjava"); // 将上面构造好的类写入到f:/myjava中
System.out.println("生成类,成功!");
这里的代码是直接新建一个class文件,当然你也可以修改一个class文件,只需要调用别的方法就可以了。
自定义类加载器
每一个class文件都需要被类加载器加载到内存中,在编译完成之后,我们可以用反射中的Class.forname()方法直接将class文件加载到内存中,当然这个时候调用的是Java默认的类加载器,我们也可以通过继承ClassLoader类来自定义一个类加载器,以实现我们需要的一些特殊的功能,比如要给我们的class文件加密解密操作。
ps:jdk默认的类加载器采用的是双亲委派机制,原理是在一个类加载器拿到一个需要加载的class文件时,首先交给它的父类去加载,如果父类没有加载这个类,那它就会自己加载这个类,父类也是这个原理。
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class fileclassloader{
public static void main(String[] args) throws Exception {
String rootdir="f:/myjava";
String root="f:/myjava/other/Emp.java";
//动态编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null, null, null,root);
System.out.println(result==0?"编译成功":"编译失败");
//类加载器加载类
FileSystemClassLoader loader=new FileSystemClassLoader(rootdir);
Class<?> c=loader.loadClass("other.Emp");
Object obj=c.newInstance();
System.out.println(c.hashCode());
System.out.println(obj);
}
}
/**
* 自定义文件系统类加载器
* @author
*
*/
class FileSystemClassLoader extends ClassLoader {
//test.User --> d:/myjava/ /test/User.class
private String rootDir;
public FileSystemClassLoader(String rootDir){
this.rootDir = rootDir;
}
//一个重写方法,在ClassLoader加载类时会用到,会自动被调用
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//应该要先查询有没有加载过这个类。如果已经加载,则直接返回加载好的类。如果没有,则加载新的类。
Class<?> c = findLoadedClass(name);
if(c!=null){
return c;
}else{
//找到父类
ClassLoader parent = this.getParent();
try {
c = parent.loadClass(name); //委派给父类加载
} catch (Exception e) {
// e.printStackTrace();
}
if(c!=null){
return c;//父类加载成功
}else{
//父类加载不成功,自己加载
byte[] classData = getClassData(name);
if(classData==null){
throw new ClassNotFoundException();
}else{
c = defineClass(name, classData, 0,classData.length);
}
}
}
return c;
}
//将一个class文件转化为字节数组
private byte[] getClassData(String classname){ //test.User d:/myjava/ test/User.class
String path = rootDir +"/"+ classname.replace('.', '/')+".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try{
is = new FileInputStream(path);
byte[] buffer = new byte[1024];
int temp=0;
while((temp=is.read(buffer))!=-1){
baos.write(buffer, 0, temp);
}
return baos.toByteArray();
}catch(Exception e){
e.printStackTrace();
return null;
}finally{
try {
if(is!=null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(baos!=null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
反射
在类加载器将class文件加载到内存后,类的信息会写在jvm的方法区,同时会在堆内存中生成一个Class对象,这个对象就代表着方法区中的这个类的信息。我们可以通过反射得到这个对象,可以查看它的属性、方法、构造器等等,最重要的是可以动态的创建它的对象,目前在很多框架中都会使用到反射。
相比于前三种方法,反射是用的最多的一种方法,因为只要我们的java文件放在工程目录下,它就会被自动编译,不需要我们动态编译,字节码操作和自定义类加载器在一般情况下也不会用到。所以更多时候用的还是反射。