类加载器(class loader)

来源:互联网 发布:昆山杜克大学 知乎 编辑:程序博客网 时间:2024/05/16 17:00

类加载器(class loader)

类加载器(class loader)用来加载Java类到java虚拟机中。
类加载器负责读取java字节码(.class文件),并转换成java.lang.Class类的一个实例。

java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类
BootStrap,ExtClassLoader,AppClassLoader
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,
这正是BootStrap。

Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个
父级类加载器对象或默认采用系统类加载器为其父类加载器。

//打印类加载器树状结构
        ClassLoader loader = TestClassIdentity.class.getClassLoader();
        while (loader != null) {
            System.out.println(loader.toString());
            loader = loader.getParent();
        }

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


类加载器的委托机制

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

每个类加载器加载类时,又先委托给其上级类加载器。
当所有的祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类的儿子。
因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?

自定义类加载器

自定义类加载器必须继承ClassLoader
findClass(String name)方法,使用指定的名称查找类。父类加载器没有加载到类,则这个方法被loadClass方法调用。
loadClass方法,先通过父类加载器加载类,最后调用findClass加载。(此处采用模板方法设计模式。子类覆盖findClass即可)
defineClass(byte[] b, int off, int len)将一个 byte 数组转换为 Class 类的实例。

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

自定义的类加载器
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader {
    public static void  main(String[] args) throws Exception {
        String srcPath=args[0];
        String descDir=args[1];
        cypherClass(srcPath,descDir);
    }
    
    //加密文件
    public static void cypherClass(String srcPath,String descDir) throws Exception
    {
        String descFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1);
        String descPath=descDir+"\\"+descFileName;
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(descPath);
        cypher(fis, fos);
        fis.close();
        fos.close();
    }
    
    //加密流中数据
    public static void cypher(InputStream ips,OutputStream ops) throws Exception {
        int b =-1;
        while ((b=ips.read())!=-1) {
            ops.write(b^0xff);
        }
    }
    private String classDir;
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //System.out.println("myclassloader");
        //System.out.println(name);
        String classFileName=classDir+"\\"+name.substring(name.lastIndexOf(".")+1)+".class";
        //String classFileName=classDir+"\\"+name+".class";
        try {
            FileInputStream fis = new FileInputStream(classFileName);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            cypher(fis, bos);
            fis.close();
            byte[] bytes= bos.toByteArray();
            return defineClass(name,bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
        //return super.findClass(name);
    }
    
    public MyClassLoader(){
        
    }
    
    public MyClassLoader(String classDir){
        this.classDir=classDir;
    }
}

用自定义的类加载器加载的类
import java.util.Date;
public class ClassLoaderAttachment extends Date{
    public String toString(){
        return "hello,leon";  
    }
}

用自定义的类加载器,加载ClassLoaderAttachment
代码中不能用ClassLoaderAttachment定义引用变量,否则会用系统加载器加载,加载不到而报错。
 Class cls= new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment");  

//用父类或者接口的方式调用
Date d= (Date)cls.newInstance();
System.out.println(d.toString());

//完全用反射的方式调用
Method methodtoString = cls.getMethod("toString");
String str = (String)methodtoString.invoke(cls.newInstance(), null);
System.out.println(str);

线程上下文类加载器

线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

Class.forName

Class.forName是一个静态方法,同样可以用来加载类。该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。第一种形式的参数 name表示的是类的全名;initialize表示是否初始化类;loader表示加载时使用的类加载器。第二种形式则相当于设置了参数 initialize的值为 trueloader的值为当前类的类加载器。Class.forName的一个很常见的用法是在加载数据库驱动的时候。如Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用来加载 Apache Derby 数据库的驱动。
 

网络类加载器

下面将通过一个网络类加载器来说明如何通过类加载器来实现组件的动态更新。即基本的场景是:Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。通过类加载器可以比较简单的实现这种需求。

类 NetworkClassLoader负责通过网络下载 Java 类字节代码并定义出 Java 类。它的实现与 FileSystemClassLoader类似。在通过NetworkClassLoader加载了某个版本的类之后,一般有两种做法来使用它。第一种做法是使用 Java 反射 API。另外一种做法是使用接口。需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。使用 Java 反射 API 可以直接调用 Java 类的方法。而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。网络类加载器的具体代码见 下载。


类什么时候被加载/类加载时机
类被使用的时候加载
第一:生成该类对象的时候,会加载该类及该类的所有父类;
第二:访问该类的静态成员的时候;
第三:用类加载器加载的时候,class.forName("类名")或者用自定义类加载器加载;

参考资料:
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/
原创粉丝点击