Classloader解析&总结

来源:互联网 发布:淘宝同城交易发货快吗 编辑:程序博客网 时间:2024/05/19 00:13

——《java程序员面试宝典》解惑

                                                          文/54dabang

昨天晚上在看《java程序员面试宝典》时候,发现里面 Classloader知识点介绍的非常含糊,今天早上又重新从网上搜了相关资料,并且梳理了一下,希望对想了解这一块的同学有所帮助。需要注意一点是,有些地方我也没有进行深入研究,在此标出。

一 What? 什么是classloader

1.定义:在jvm中负责加载java class文件的部分。      

2.有多种

1) Bootstrap ClassLoader本地代码(根据操作系统,非java编写)实现,负责加载核心javaAPI(即以java.*.*开头的所有类)   以及下面的两个classLoaderExtClassLoaderAppClassLoader

  (2ExtClassLoader (全称 Extension ClassLoadr扩展类加载器) 加载扩展API (例如 jREext目录下的类)    

   (3)  AppClassLoader 负责加载用户自定义的应用程序类,即我们自己写的程序,编译后存放在CLASSPATH目录下的*.class文件

3.加载顺序

   JVM启动-----àBootstrap classLoader加载---àExtClassLoader加载 ----àAppClassLoader加载

   注意:后两个classLoader都是由Bootstrap classLoader来启动的

4.何时使用classLoader

当你使用java去执行一个类,JVM使用ApplicationClassLoader加载这个类;然后如果类A引用了类B,不管是直接引用还是用Class.forName()引用,JVM就会找到加载类AClassLoader,并用这个ClassLoader来加载类BJVM按照运行时的有效执行语句,来决定是否需要装载新类,从而装载尽可能少的类,这一点和编译类是不相同的。

即类引用类B(引用方式含有两种:Class.forName()引用或者直接使用变量来引用)

Jvm-->通过ApplicationCalssloader加载类A------->通过AclassLoader加载类B

 

二 why 为什么要自己写classLoader

  因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,如果我们的class文件从网络、数据库等其他渠道来获取时候,我们就需要自己写classLoader,方法是要重载父类的findClass()。

自定义classLoader可以实现以下目标:

1)在执行非置信代码之前,自动验证数字签名(安全保证)

2)动态地创建符合用户特定需要的定制化构建类

3)从特定的场所取得java class,例如数据库中、网络中进行加载。

 

三 How 如何写自己的classLoader

  (1)需要继承java.lang.ClassLoader或者它的子类

  (2)重写findClass()方法。

四 classLoader加载的方式

   (1)使用双亲委托模式进行类加载。

   自定义ClassLoader都必须继承ClassLoader这个抽象类,而每个ClassLoader都会有一个parent ClassLoader,我们可以看一下ClassLoader这个抽象类中有一个getParent()方法,这个方法用来返回当前ClassLoaderparent,注意,这个parent不是指的被继承的类,而是在实例化该ClassLoader时指定的一个ClassLoader如果这个parentnull,那么就默认该ClassLoaderparentbootstrap classloader这个parent有什么用呢? 

   看一下classLoader的源代码:

protected synchronized Class loadClass(String name, boolean resolve)throws ClassNotFoundException    {// 首先检查该name指定的class是否有被加载Class c = findLoadedClass(name);if (c == null) {    try {if (parent != null) {    //如果parent不为null,则调用parent的loadClass进行加载c = parent.loadClass(name, false);} else {//parent为null,则调用BootstrapClassLoader进行加载    c = findBootstrapClass0(name);}    } catch (ClassNotFoundException e) {        //如果仍然无法加载成功,则调用自身的findClass进行加载        c = findClass(name);//用户自定义的    }}if (resolve) {    resolveClass(c);}return c;    }

说明:我们使用这个自定义的ClassLoader加载java.lang.String,那么这里String不会被这个ClassLoader加载。原因是:

  这里使用了委托模式,根据上述代码。我们进行分情况讨论。如果parent!=null,那么则会调用parent.loadClass()方法,一直递归,所有的classLoader类都继承自BootstrapClassLoader,那么最终会调用BootstrapClassLoader

 

findBootstrapClass0(name);方法。如果parent==null那么就会直接调用

BootstrapClassLoader方法。不论哪种方式,都会最终调用BootstrapClassLoader的方法。

2)这样做的好处(安全、避免程序加载

     第一个原因就是因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。 

    第二个原因就是考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader 

 

五 自定义classLoader代码实现

较好的一篇博客

http://rainlife.iteye.com/blog/70072

以下是从本地读取classLoader代码。

package com.cmw.example.one;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method; /** * 文件加载类 * 可根据MyFileClassLoader 从文件中动态生成类 * @author chengmingwei * */public class MyFileClassLoader extends ClassLoader {   private String classPath;   @SuppressWarnings("unchecked") public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{ MyFileClassLoader fileClsLoader = new MyFileClassLoader(); fileClsLoader.setClassPath("E:\\j2ee_proj\\skythink\\WebContent\\WEB-INF\\classes\\"); Class cls = fileClsLoader.loadClass("com.cmw.entity.sys.AccordionEntity"); Object obj = cls.newInstance(); Method[] mthds = cls.getMethods(); for(Method mthd : mthds){ String methodName = mthd.getName(); System.out.println("mthd.name="+methodName); } System.out.println("obj.class="+obj.getClass().getName()); System.out.println("obj.class="+cls.getClassLoader().toString()); System.out.println("obj.class="+cls.getClassLoader().getParent().toString()); }   /**  * 根据类名字符串从指定的目录查找类,并返回类对象  */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = null; try { classData = loadClassData(name); } catch (IOException e) { e.printStackTrace(); } return super.defineClass(name, classData, 0, classData.length);//调用父类方法 固定格式 }   /**  * 根据类名字符串加载类 byte 数据流  * @param name   类名字符串  例如: com.cmw.entity.SysEntity  * @return   返回类文件 byte 流数据  * @throws IOException  */ private byte[] loadClassData(String name) throws IOException{ File file = getFile(name); FileInputStream fis = new FileInputStream(file); byte[] arrData = new byte[(int)file.length()];//一次性读取文件到二进制数组 fis.read(arrData); return arrData; }   /**  * 根据类名字符串返回一个 File 对象  * @param name   类名字符串     * @return    File 对象  * @throws FileNotFoundException  */ private File getFile(String name) throws FileNotFoundException {//获取文件 File dir = new File(classPath); if(!dir.exists()) throw new FileNotFoundException(classPath+" 目录不存在!"); String _classPath = classPath.replaceAll("[\\\\]", "/"); int offset = _classPath.lastIndexOf("/"); name = name.replaceAll("[.]", "/"); if(offset != -1 && offset < _classPath.length()-1){ _classPath += "/"; } _classPath += name +".class"; dir = new File(_classPath); if(!dir.exists()) throw new FileNotFoundException(dir+" 不存在!"); return dir; }  public String getClassPath() { return classPath; }  public void setClassPath(String classPath) { this.classPath = classPath; }   }   
上述代码中最主要的思想是从*.class文件中读取二进制的文件到classData数组当中,然后重写findClass方法:
 super.defineClass(name, classData, 0, classData.length);,在调用时直接使用classLoader.loadclass,父类会在内部实现中调用我们重写的findClass方法。
0 0