动态编译、字节码操作、自定义类加载器、反射

本文总结了Java中的动态编译、字节码操作、自定义类加载器和反射技术。动态编译是将Java源文件转化为class文件,而字节码操作允许直接修改class文件内容,提供了比反射更大的灵活性。自定义类加载器允许扩展加载机制,如加密解密class文件。反射则用于运行时获取和操作类信息,常用于框架开发。通常,反射是最常用的技术,因为它在默认情况下即可工作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章是我自己的学习总结,适合有一定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文件放在工程目录下,它就会被自动编译,不需要我们动态编译,字节码操作和自定义类加载器在一般情况下也不会用到。所以更多时候用的还是反射。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值