类加载器ClassLoader
首先认识Class文件
我们平常用文本编辑器或者是IDE编写的程序都是.java格式的文件,这是最基础的源码,但这类文件是不能直接运行的。我们需要在命令行中进行java文件的编译javac HelloWorld.java,生成了.class文件,从命令行中执行命令:java HelloWorld,运行程序。
因为class文件是字节码格式文件,java虚拟机并不能直接识别我们平常编写的.java源文件,所以需要javac这个命令转换成.class文件。
JAVA类加载简单流程
Java语言系统自带有三个类加载器:
- Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path
被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录。
- Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs
选项指定的目录。
- Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。
我们上面简单介绍了3个ClassLoader。说明了它们加载的路径。并且还提到了-Xbootclasspath
和-D java.ext.dirs
这两个虚拟机参数选项。
下面是ClassLoader中的一段代码
一些重要的方法
类加载全过程
package com.bjsxt.test; public class Demo01 { static{ System.out.println("静态初始化Demo01"); } public static void main(String[] args) throws Exception { System.out.println("Demo01的main方法!"); System.out.println(System.getProperty("java.class.path")); //主动引用 // new A(); // System.out.println(A.width); // Class.forName("com.bjsxt.test.A"); //被动引用 // System.out.println(A.MAX); // A[] as = new A[10]; System.out.println(B.width); } } class B extends A { static { System.out.println("静态初始化B"); } } class A extends A_Father { public static int width=100; //静态变量,静态域 field public static final int MAX=100; static { System.out.println("静态初始化类A"); width=300; } public A(){ System.out.println("创建A类的对象"); } } class A_Father extends Object { static { System.out.println("静态初始化A_Father"); } }
自定义类加载器
package com.bjsxt.test; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** * 自定义文件系统类加载器 * */ public class FileSystemClassLoader extends ClassLoader { //com.bjsxt.test.User --> d:/myjava/ com/bjsxt/test/User.class private String rootDir; public FileSystemClassLoader(String rootDir){ this.rootDir = rootDir; } @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; } private byte[] getClassData(String classname){ //com.bjsxt.test.User d:/myjava/ com/bjsxt/test/User.class String path = rootDir +"/"+ classname.replace('.', '/')+".class"; // IOUtils,可以使用它将流中的数据转成字节数组 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(); } } } } 测试类的代码如下: package com.bjsxt.test; public class Demo03 { public static void main(String[] args) throws Exception{ FileSystemClassLoader loader = new FileSystemClassLoader("d:/myjava"); FileSystemClassLoader loader2 = new FileSystemClassLoader("d:/myjava"); Class<?> c = loader.loadClass("com.bjsxt.gaoqi.HelloWorld"); Class<?> c2 = loader.loadClass("com.bjsxt.gaoqi.HelloWorld"); Class<?> c3 = loader2.loadClass("com.bjsxt.gaoqi.HelloWorld"); Class<?> c4 = loader2.loadClass("java.lang.String"); Class<?> c5 = loader2.loadClass("com.bjsxt.test.Demo01"); System.out.println(c.hashCode()); System.out.println(c2.hashCode()); System.out.println(c3.hashCode()); //同一个类,被不同的加载器加载,JVM认为也是不相同的类 System.out.println(c4.hashCode()); System.out.println(c4.getClassLoader()); //引导类加载器 System.out.println(c3.getClassLoader()); //自定义的类加载器 System.out.println(c5.getClassLoader()); //系统默认的类加载器 } } 网络类加载器: package com.bjsxt.test; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; /** * 网络类加载器 */ public class NetClassLoader extends ClassLoader { //com.bjsxt.test.User --> www.sxt.cn/myjava/ com/bjsxt/test/User.class private String rootUrl; public NetClassLoader(String rootUrl){ this.rootUrl = rootUrl; } @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; } private byte[] getClassData(String classname){ //com.bjsxt.test.User d:/myjava/ com/bjsxt/test/User.class String path = rootUrl +"/"+ classname.replace('.', '/')+".class"; // IOUtils,可以使用它将流中的数据转成字节数组 InputStream is = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try{ URL url = new URL(path); is = url.openStream(); 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(); } } } }
类加载器之加密技术初步
自定义加密解密类加载器技术展示的代码如下: package com.bjsxt.test; /** * 测试简单加密解密(取反)操作 * @author 尚学堂高淇 www.sxt.cn * */ public class Demo04 { public static void main(String[] args) throws Exception { //测试取反操作 // int a = 3; //0000011 // System.out.println(Integer.toBinaryString(a^0xff)); //加密后的class文件,正常的类加载器无法加载,报classFormatError // FileSystemClassLoader loader = new FileSystemClassLoader("d:/myjava/temp"); // Class<?> c = loader.loadClass("HelloWorld"); // System.out.println(c); DecrptClassLoader loader = new DecrptClassLoader("d:/myjava/temp"); Class<?> c = loader.loadClass("HelloWorld"); System.out.println(c); } } 加密类加载器代码: package com.bjsxt.test; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * 加密工具类 * @author 尚学堂高淇 www.sxt.cn * */ public class EncrptUtil { public static void main(String[] args) { encrpt("d:/myjava/HelloWorld.class", "d:/myjava/temp/HelloWorld.class"); } public static void encrpt(String src, String dest){ FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(src); fos = new FileOutputStream(dest); int temp = -1; while((temp=fis.read())!=-1){ fos.write(temp^0xff); //取反操作 } } catch (Exception e) { e.printStackTrace(); }finally{ try { if(fis!=null){ fis.close(); } } catch (IOException e) { e.printStackTrace(); } try { if(fos!=null){ fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } } 解密的类加载器代码如下: package com.bjsxt.test; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** * 加载文件系统中加密后的class字节码的类加载器 * @author 尚学堂高淇 www.sxt.cn * */ public class DecrptClassLoader extends ClassLoader { //com.bjsxt.test.User --> d:/myjava/ com/bjsxt/test/User.class private String rootDir; public DecrptClassLoader(String rootDir){ this.rootDir = rootDir; } private byte[] getClassData(String classname){ //com.bjsxt.test.User d:/myjava/ com/bjsxt/test/User.class String path = rootDir +"/"+ classname.replace('.', '/')+".class"; // IOUtils,可以使用它将流中的数据转成字节数组 InputStream is = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try{ is = new FileInputStream(path); int temp = -1; while((temp=is.read())!=-1){ baos.write(temp^0xff); //取反操作,相当于解密 } 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(); } } } }