Java高新技术之类加载器

来源:互联网 发布:猎头书籍推荐 知乎 编辑:程序博客网 时间:2024/06/05 23:28

概念

        类加载器就是加载类的工具。在java程序中用到一个类如:System,java虚拟机需要先把类的字节码加载到内存里面来通常这个字节码原始文件也就是.class文件存放在硬盘上的classpath指定的目录下,我们需要把这个.class文件从硬盘上加载进来,再进行一些处理,处理完的结果就是字节码,这一系列工作就是类加载器做的。Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类BootStrap,ExtClassLoader,AppClassLoader。类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap。

我们通过代码可以得到某一个类的类加载器

public class ClassLoderDemo {public static void main(String[] args) {System.out.println(ClassLoderDemo.class.getClassLoader().getClass().getName());System.out.println(System.class.getClassLoader());//BootStrap加载器}}
打印结果:sun.misc.Launcher$AppClassLoader

null

说明ClassLoderDemo的加载器是AppClassLoader,System的加载器返回null,并不是说明其没有加载器,而是其加载器是BootStrap

Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。我们可以通过代码得到加载当前类的类加载器和其所有父级类加载器

public class ClassLoderDemo {public static void main(String[] args) {ClassLoader loder=ClassLoderDemo.class.getClassLoader();while(loder!=null){System.out.println(loder.getClass().getName());loder=loder.getParent();}System.out.println(loder);}}
打印结果:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null

类加载器之间的父子关系和管辖范围图



如果我们将类ClassLoderDemo打包成jar文件放在%JAVA_HOME%\jre\lib\ext\目录下那么上面打印结果为

sun.misc.Launcher$ExtClassLoader
null

委托机制

当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。 
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。

有一道面试,能不能自己写个类叫java.lang.System?
答:通常不可以java类加载器的委托机制会委托给父级别的类加载器进行加载,而最高级别的类加载器BootStrap会在rt.jar文件中加载到java.lang.System这个类,所以你自己写的System类就不会被加载,当然有时候也是可以的,比如我可以自定义一个类加载器,只能从我这里加载

自定义类加载器

自定义的类加载器的必须继承ClassLoader
复写loadClass方法与findClass方法获取class文件
通过defineClass方法将class文件转换成字节码文件

需求:用自定义类加载器加载某一个类,并且对该字节码文件进行加密。步骤

编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。

代码:

public class MyClassLoader extends ClassLoader{public static void main(String[] args) throws IOException {String srcPath=args[0];String descDir=args[1];FileInputStream fis=new FileInputStream(srcPath);String descFileName=srcPath.substring(srcPath.lastIndexOf('\\')+1);String descPath=descDir+"\\"+descFileName;FileOutputStream fos=new FileOutputStream(descPath);encipher(fis,fos);fis.close();fos.close();}//加密方法public static void encipher(InputStream in,OutputStream out) throws IOException{int buf=0;while((buf=in.read())!=-1){out.write(buf^0xff);//异或,原来是0变成1,1变成0}}private String classDir;@Override//复写了findClass方法,如果父类找不到文件就会使用自定义类加载器的方法进行加载protected Class<?> findClass(String name) throws ClassNotFoundException {String fileName=classDir+"\\"+name+".class";try {FileInputStream fis=new FileInputStream(fileName);ByteArrayOutputStream bos=new ByteArrayOutputStream();// //对文件进行解密 encipher(fis,bos);fis.close();byte [] bytes=bos.toByteArray();return defineClass(bytes, 0, bytes.length);} catch (Exception e) {e.printStackTrace();}return super.findClass(name);}public MyClassLoader(){}public MyClassLoader(String classDir){this.classDir=classDir;}}

原创粉丝点击