一 点睛

1 ClassLoader类有如下两个关键方法:

loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。

findClass(String name):根据二进制名称来查找类。

如果需要实现自定义的ClassLoader,可以通过重写以上两个方法来实现,当然我们推荐重写findClass()方法,而不是重写loadClass()方法。

2 自定义类加载器常用功能

  • 执行代码前自动验证数字签名。
  • 根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译class文件。
  • 根据用户需求来动态地加载类。
  • 根据应用需求把其他数据以字节码的形式加载到应用中。

二 实战

1 CompileClassLoader.java

import java.io.*;
import java.lang.reflect.*;

public class CompileClassLoader extends ClassLoader
{
   // 读取一个文件的内容
   private byte[] getBytes(String filename)
         throws IOException
   {
      File file = new File(filename);
      long len = file.length();
      byte[] raw = new byte[(int)len];
      try(
            FileInputStream fin = new FileInputStream(file))
      {
         // 一次读取class文件的全部二进制数据
         int r = fin.read(raw);
         if(r != len)
            throw new IOException("无法读取全部文件:"
                  + r + " != " + len);
         return raw;
      }
   }
   // 定义编译指定Java文件的方法
   private boolean compile(String javaFile)
         throws IOException
   {
      System.out.println("CompileClassLoader:正在编译 "
            + javaFile + "...");
      // 调用系统的javac命令
      Process p = Runtime.getRuntime().exec("javac " + javaFile);
      try
      {
         // 其他线程都等待这个线程完成
         p.waitFor();
      }
      catch(InterruptedException ie)
      {
         System.out.println(ie);
      }
      // 获取javac线程的退出值
      int ret = p.exitValue();
      // 返回编译是否成功
      return ret == 0;
   }
   // 重写ClassLoader的findClass方法
   protected Class<?> findClass(String name)
         throws ClassNotFoundException
   {
      Class clazz = null;
      // 将包路径中的点(.)替换成斜线(/)。
      String fileStub = name.replace("." , "/");
      String javaFilename = fileStub + ".java";
      String classFilename = fileStub + ".class";
      File javaFile = new File(javaFilename);
      File classFile = new File(classFilename);
      // 当指定Java源文件存在,且class文件不存在、或者Java源文件
      // 的修改时间比class文件修改时间更晚,重新编译
      if(javaFile.exists() && (!classFile.exists()
            || javaFile.lastModified() > classFile.lastModified()))
      {
         try
         {
            // 如果编译失败,或者该Class文件不存在
            if(!compile(javaFilename) || !classFile.exists())
            {
               throw new ClassNotFoundException(
                     "ClassNotFoundExcetpion:" + javaFilename);
            }
         }
         catch (IOException ex)
         {
            ex.printStackTrace();
         }
      }
      // 如果class文件存在,系统负责将该文件转换成Class对象
      if (classFile.exists())
      {
         try
         {
            // 将class文件的二进制数据读入数组
            byte[] raw = getBytes(classFilename);
            // 调用ClassLoader的defineClass方法将二进制数据转换成Class对象
            clazz = defineClass(name,raw,0,raw.length);
         }
         catch(IOException ie)
         {
            ie.printStackTrace();
         }
      }
      // 如果clazz为null,表明加载失败,则抛出异常
      if(clazz == null)
      {
         throw new ClassNotFoundException(name);
      }
      return clazz;
   }
   // 定义一个主方法
   public static void main(String[] args) throws Exception
   {
      // 如果运行该程序时没有参数,即没有目标类
      if (args.length < 1)
      {
         System.out.println("缺少目标类,请按如下格式运行Java源文件:");
         System.out.println("java CompileClassLoader ClassName");
      }
      // 第一个参数是需要运行的类
      String progClass = args[0];
      // 剩下的参数将作为运行目标类时的参数,
      // 将这些参数复制到一个新数组中
      String[] progArgs = new String[args.length-1];
      System.arraycopy(args , 1 , progArgs
            , 0 , progArgs.length);
      CompileClassLoader ccl = new CompileClassLoader();
      // 加载需要运行的类
      Class<?> clazz = ccl.loadClass(progClass);
      // 获取需要运行的类的主方法
      Method main = clazz.getMethod("main" , (new String[0]).getClass());
      Object[] argsArray = {progArgs};
      main.invoke(null,argsArray);
   }
}

2 Hello.java

public class Hello
{
   public static void main(String[] args)
   {
      for (String arg : args)
      {
         System.out.println("运行Hello的参数:" + arg);
      }
   }
}

3 运行

E:\Java\疯狂java讲义\codes\18\18.2>java CompileClassLoader Hello 自定义加载器
CompileClassLoader:正在编译 Hello.java...
运行Hello的参数:自定义加载器