欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Online Judge】2.类加载器

程序员文章站 2022-03-09 15:17:25
...

困惑

接着说上一篇 动态编译 的问题

在网上看到有人用的是 ProcessBuilder 然后执行 start()方法最终得到一个 Process 对象,然后再使用waitFor等3秒后看看执行结束没有,貌似也行。用cmd的话自己都不要手动去开线程了,等它3秒再慢慢处理。但是回想起来用这个方法计时有点困难啊,再插入一段代码到其中?经过深思 之后,我觉得还是用JavaCompiler方便,控制要执行的main方法,很多繁琐的问题都不用考虑了。。。


双亲委派模型

记得我第一次接触到这个名词的时候是在校招选择题上看到的,问的是这个模型有什么特点,当时就蒙了,这是啥?Java有这个东西吗?我怎么没见过?

那天回去之后恶补了一下这个知识点,才知道有类加载器这种东西…

【Online Judge】2.类加载器

应用程序是由三种类加载器互相配合从而实现类加载,除此之外还可以加入自己定义的类加载器。

下图展示了类加载器之间的层次关系,称为双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系(Composition)来实现,而不是继承关系(Inheritance)。

图文来源 CS_Notes

类加载器 ClassLoader

让我们看一下 JDK 文档里对类加载器的描述:

类加载器是负责加载类的对象。 ClassLoader类是一个抽象类。 给定一个类的binary name ,类加载器应该尝试定位或生成构成类的定义的数据。 典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
每个类对象包含reference来定义它的ClassLoader

ClassLoader类使用委托模式来搜索类和资源。 ClassLoader的每个实例都有一个关联的父类加载器。 当请求查找类或资源时, ClassLoader实例将在尝试查找类或资源本身之前将类或资源的搜索委托给其父类加载器。 虚拟机的内置类加载器(称为“引导类加载器”)本身不具有父级,但可以作为ClassLoader实例的父级。

官方文档已经说得很明白了,主要原理还是靠读取文件来实现的,估计Class.forname()也是调用了类加载器,其中也说了委派模型,当然这不是我们这次想要的东西,我们的目的是加载之前已经编译好的class文件,再往下看:

网络类加载器子类必须定义从网络加载类的方法findClass和loadClassData 。 一旦下载构成类的字节,它应该使用方法defineClass创建一个类实例。 示例实现是: 

     class NetworkClassLoader extends ClassLoader {
         String host;
         int port;

         public Class findClass(String name) {
             byte[] b = loadClassData(name);
             return defineClass(name, b, 0, b.length);
         }

         private byte[] loadClassData(String name) {
             // load the class data from the connection
              . . .
         }
     }

貌似还可以从网络流中获取数据…花式加载?我们需求比较简单,把代码封装到一个byte[]数组里面返回就可以了,代码如下:

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;


public class MyClassLoader extends ClassLoader {

    /** 要加载的 Java 类的 classpath 路径 */
    private String classpath;

    public MyClassLoader(String classpath) {
        // 指定父加载器
        super(ClassLoader.getSystemClassLoader());
        this.classpath = classpath;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = this.loadClassData(name);
        return this.defineClass(name, data, 0, data.length);
    }

    /**
     * 加载 class 文件中的内容
     *
     * @param name
     * @return
     */
    private byte[] loadClassData(String name) {
        try {
            // 传进来是带包名的
            name = name.replace(".", "//");
            FileInputStream inputStream = new FileInputStream(new File(classpath + name + ".class"));
            // 定义字节数组输出流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while ((b = inputStream.read()) != -1) {
                baos.write(b);
            }
            inputStream.close();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

整合

类加载只需要一次,执行可能会有多次

public class JavaJudge {
	
	private MyClassLoader classLoader;
    // 类文件保存路径
	private String class_path;
    // 类名
	private String class_name;
	
	JavaJudge(String class_path,String class_name){
		this.class_path = class_path;
		this.class_name = class_name;
		classLoader = new MyClassLoader(class_path);
	}
	
	
	public Class<?> getCodeClass(String code){
		
		boolean flag = JavaCompilerUtil.CompilerJavaFile(class_name, code, class_path);
		if (!flag) {
            //抛出错误信息
			throw new RuntimeException(JavaCompilerUtil.writer.toString());
		}else {
			try {
				return classLoader.findClass(class_name);
			} catch (ClassNotFoundException e) {
				throw new RuntimeException("500,类加载失败!");
			}
		}
	}
}

拿到了 Class 对象,想要执行还是蛮简单的,而且我们要求用户以main函数为入口

Class clzz = .....
Method method = clzz.getMethod("main", String[].class);
method.invoke(clzz, new Object[]{new String[]{""}});

这个以后再加进去,后续还有很多的判断要处理