虽然在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。下面将通过两个具体的实例来说明类加载器的开发。
下面,详细的介绍自定义一个类加载器的过程。
一、首先,写出一个接口,然后用一个类实现该接口,该类作为测试类,即我们自定义ClassLoader要加载的类。
接口:
/** * 要加载类的接口,加载该接口的子类时,可以用接口引用,而不需要利用反射来实现。 */ public interface InterfaceTest { public void name(); public void age(); }
/** * 测试类,自定义类加载器去加载该类 */ public class ClassTest implements InterfaceTest{ @Override public void name() { System.out.println("tao"); } @Override public void age() { System.out.println("21"); } }
找到我们要加密的.class文件的位置:E:\workspace.fu\ClassLoaderTest\bin\com\tao\test\ClassTest.class
加密后的.class文件要存储的位置,这里将它直接放到E盘根目录:E:\ClassTest.class
/** * 用加密算法生成要隐藏的字节码文件 */ public class ClassEncrypt extends MyClassLoader{ public static void main(String[] args) throws IOException { //要加密的字节码.class文件 String srcPath="E:/workspace.fu/ClassLoaderTest/bin/com/tao/test/ClassTest.class"; //加密之后输出的字节码.class文件的位置 String destPath="E:/ClassTest.class"; FileInputStream fis=new FileInputStream(srcPath); FileOutputStream ofs=new FileOutputStream(destPath); cypher(fis, ofs);//加密 fis.close(); ofs.close(); } //简单的加密,用于测试。将所有二进制位取反,即0变成1,1变成0 private static void cypher(InputStream in,OutputStream out) throws IOException{ int b=-1; while((b=in.read())!=-1){ out.write(b^0xff); } } }
可以将E:\workspace.fu\ClassLoaderTest\bin\com\tao\test的ClassTest.class文件删除,并且删除ClassTest.java文件和加密类ClassEncrypt.java。
三、编写我们自己的类加载器,必须继承ClassLoader,然后覆盖findClass()方法。
ClassLoader超类的loadClass方法用于将类的加载操作委托给父类加载器去进行,只有该类尚未加载并且父类加载器也无法加载该类时,才调用findClass()方法。
如果要实现该方法,必须做到以下几点:
(1)、为来自本地文件系统或者其他来源的类加载其字节码
(2)、调用ClassLoader超类的defineClass()方法,向虚拟机提供字节码
/** * 自定义的类加载器 */ public class MyClassLoader extends ClassLoader{ /** * 因为类加载器是基于委托机制,所以我们只需要重写findClass方法。 * 它会自动向父类加载器委托,如果父类没有找到,就会再去调用我们重写的findClass方法加载 */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { //需要加载的.class字节码的位置 String classPath="E:/ClassTest.class"; FileInputStream fis=new FileInputStream(classPath); ByteArrayOutputStream bos=new ByteArrayOutputStream(); cypher(fis, bos); fis.close(); byte[] bytes=bos.toByteArray(); return defineClass(bytes, 0, bytes.length); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return super.findClass(name); } //相应的字节码解密类,在加载E盘根目录下的被加密过的ClassTest.class字节码的时候,进行相应的解密。 private static void cypher(InputStream in,OutputStream out) throws IOException{ int b=-1; while((b=in.read())!=-1){ out.write(b^0xff); } } }
public class Test { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class clazz=new MyClassLoader().loadClass("com.tao.test.ClassTest"); //这就是我们接口的作用。如果没有接口,就需要利用反射来实现了。 InterfaceTest classTest=(InterfaceTest) clazz.newInstance(); classTest.name(); classTest.age(); } }