类加载机制ClassLoader

在Java环境中,有个概念叫类加载器。(ClassLoader)就是先加载后使用。我们每使用一个对象,则先有类,类生对象。我们要使用类,则先将类加载其中。加载的是啥?加载的是字节码。我们开发的流程时。一般先定义一个类,譬如 Student.Java 后缀名为.java的一个文件。

public class Student{

    private String name;

    public void setName(String name) {
        this.name = name;
    }
}

编译。 命令用javac, Eclipse AndroidStudio会自动帮我们编译。

类加载机制ClassLoader_第1张图片
生成 Student.class文件
类加载机制ClassLoader_第2张图片

Student.class类文件用文本打开 是一段二进制字节码
类加载机制ClassLoader_第3张图片

**
Java类加载机制是运行时加载, 我们调用java Student 就执行了代码。当执行代码程序时,就会讲.class文件加载到内存。
**
修改代码 重新编译后执行
类加载机制ClassLoader_第4张图片
类加载机制ClassLoader_第5张图片

如果我们将Student.class删除后 再执行代码,就会报找不到类的异常
这里写图片描述
我将Student.java复制到另一处 重新编译 再将字节拷贝过来 那么执行的显示内容已经发生变化。

这里写图片描述
真正的执行代码与编译的字节码有关。

类加载机制 主要是双亲委派模型
(1)Bootstrap ClassLoader : 将存放于\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用
(2) Extension ClassLoader : 将\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。
(3) Application ClassLoader : 负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用。
类加载机制ClassLoader_第6张图片
如果父类已经加载了此类,则子类就没有必要加载。同时也是为安全方面考虑。譬如我们自定义了一个java.lang.String类。如果加载了我们自己的类那么危害很大。双亲委派正是解决这样的问题。

我们做个试验。我们编译一个Student.java类成Student.class.再打成TestExtensionClassLoader.jar文件。并放入Jdk1.8.0/jre/lib/ext下
类加载机制ClassLoader_第7张图片
类加载机制ClassLoader_第8张图片
类加载机制ClassLoader_第9张图片
然后我们修改Stuent的代码输出语句

public class Student{

    private String name;

    public void setName(String name) {
        this.name = name;
    }


    public String toString(){
        return "Student :" + "name=" + name;
    }

    public static void main(String[] args) {
        Student stu = new Student();
        //stu.setName("me in C:\\Program Files\\Java\\jdk1.8.0_101\\jre\\lib\\ext");
        stu.setName("me in E:\\javaText");          
        System.out.println("out:" + stu);

    }
}

编译后再打印,发现最新的代码已经不能执行。而是执行我们TestExtensionClassLoader.jar中Student.class的语句。

public class Student{

    private String name;

    public void setName(String name) {
        this.name = name;
    }


    public String toString(){
        return "Student :" + "name=" + name;
    }

    public static void main(String[] args) {
        Student stu = new Student();
        //stu.setName("me in C:\\Program Files\\Java\\jdk1.8.0_101\\jre\\lib\\ext");
        stu.setName("me in E:\\javaText");
        ClassLoader loader = Student.class.getClassLoader();
        System.out.println("this    loader==" + loader);
        while(loader != null) {
            loader = loader.getParent();
            System.out.println("partent loader==" + loader);
        }

    }
}

我们将jar放在jre/lib/ext下输出
这里写图片描述
说明当前的ClassLoader是ExtClassLoader.
删除 jre/lib/ext 的 TestExtensionClassLoader.jar 修改代码 编译打印
类加载机制ClassLoader_第10张图片
说明当前的ClassLoader是AppClassLoader.这也验证了双亲委派模型。

这次我们将Jar放在最顶层。
这里写图片描述

执行结果
这里写图片描述
loader 为空 默认指 Bootstrap ClassLoader :

Eclipse可以这样配置同样的效果

类加载机制ClassLoader_第11张图片

类加载机制做了个简单的说明。
我们来看一下Java官方文档

/**
 * A class loader is an object that is responsible for loading classes. The
 * class ClassLoader is an abstract class.  Given the "#name">binary name of a class, a class loader should attempt to
 * locate or generate data that constitutes a definition for the class.  A
 * typical strategy is to transform the name into a file name and then read a
 * "class file" of that name from a file system.
 一个类装载器是一个负责加载类的对象。 类 ClassLoader 是一个抽象类。 鉴于类的"#name">二进制名称,类加载器应尝试查找或生成构成类定义的数据。 典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
 *
 * 

Every {@link Class Class} object contains a {@link * Class#getClassLoader() reference} to the ClassLoader that defined * it. 每个{@link Class Class }对象都包含一个{@link Class# getClassLoader()引用}定义它的 ClassLoader 。 *

Class objects for array classes are not created by class * loaders, but are created automatically as required by the Java runtime. * The class loader for an array class, as returned by {@link * Class#getClassLoader()} is the same as the class loader for its element * type; if the element type is a primitive type, then the array class has no * class loader. 数组类的类对象不是由类加载器创建的,而是根据Java运行时的需要自动创建。由{@link Class#getClassLoader()}返回的数组类的类加载器 与其类型的类加载器相同; 如果元素类型是原始类型,则数组类没有类加载器。 *

Applications implement subclasses of ClassLoader in order to * extend the manner in which the Java virtual machine dynamically loads * classes. 应用程序实现 ClassLoader 的子类,以便扩展Java虚拟机动态加载类的进程。 *

Class loaders may typically be used by security managers to indicate * security domains. * 安全管理员通常可以使用类加载器来指示安全域。 * *

The ClassLoader class uses a delegation model to search for * classes and resources. Each instance of ClassLoader has an * associated parent class loader. When requested to find a class or * resource, a ClassLoader instance will delegate the search for the * class or resource to its parent class loader before attempting to find the * class or resource itself. The virtual machine's built-in class loader, * called the "bootstrap class loader", does not itself have a parent but may * serve as the parent of a ClassLoader instance. ClassLoader 类使用委派模型来搜索类和资源。 ClassLoader 的每个实例都有一个关联的父类加载器。 当请求查找类或资源时,在尝试查找类或资源本身之前, ClassLoader 实例将委派对类或资源的搜索到其父类加载器。 虚拟机的内置类加载器(称为“引导类加载器”)本身不具有父级,但可以作为 ClassLoader 实例的父级。 *

Class loaders that support concurrent loading of classes are known as * parallel capable class loaders and are required to register * themselves at their class initialization time by invoking the * {@link * #registerAsParallelCapable ClassLoader.registerAsParallelCapable} * method. Note that the ClassLoader class is registered as parallel * capable by default. However, its subclasses still need to register themselves * if they are parallel capable.
* In environments in which the delegation model is not strictly * hierarchical, class loaders need to be parallel capable, otherwise class * loading can lead to deadlocks because the loader lock is held for the * duration of the class loading process (see {@link #loadClass * loadClass} methods). *

Normally, the Java virtual machine loads classes from the local file * system in a platform-dependent manner. For example, on UNIX systems, the * virtual machine loads classes from the directory defined by the * CLASSPATH environment variable. 通常,Java虚拟机以平台相关的方式从本地文件系统加载类。 例如,在UNIX系统上,虚拟机从由 CLASSPATH 环境变量定义的目录加载类。 ---------------------------------------------------------- 这里 这里 这里 *

However, some classes may not originate from a file; they may originate * from other sources, such as the network, or they could be constructed by an * application. The method {@link #defineClass(String, byte[], int, int) * defineClass} converts an array of bytes into an instance of class * Class. Instances of this newly defined class can be created using * {@link Class#newInstance Class.newInstance}. 然而,一些类可能不是源于一个文件; 它们可以来自诸如网络的其他来源,或者它们可以由应用构建。 方法{@link #defineClass(String,byte [],int,int) defineClass }将一个字节数组转换为类Class 的实例。 这个新定义的类的实例可以使用{@link Class#newInstance Class.newInstance }创建。 *

The methods and constructors of objects created by a class loader may * reference other classes. To determine the class(es) referred to, the Java * virtual machine invokes the {@link #loadClass loadClass} method of * the class loader that originally created the class. 类加载器创建的对象的方法和构造函数可以引用其他类。 要确定所引用的类,Java虚拟机调用最初创建该类的类加载器的{@link #loadClass loadClass }方法。 *

For example, an application could create a network class loader to * download class files from a server. Sample code might look like: 例如,应用程序可以创建一个网络类加载器来从服务器下载类文件。 示例代码可能如下所示: *

 *   ClassLoader loader = new NetworkClassLoader(host, port);
 *   Object main = loader.loadClass("Main", true).newInstance();
 *        . . .
 * 
* *

The network class loader subclass must define the methods {@link * #findClass findClass} and loadClassData to load a class * from the network. Once it has downloaded the bytes that make up the class, * it should use the method {@link #defineClass defineClass} to * create a class instance. A sample implementation is: 网络类加载器子类必须定义方法{@link #findClass findClass 和loadClassData 以从网络加载类。 一旦下载构成类的字节,它应该使用方法{@link #defineClass defineClass }创建一个类实例。 示例实现是: *

 *     class NetworkClassLoader extends ClassLoader {
 *         String host;
 *         int port;
 *
 *         public Class findClass(String name) {
 *             byte[] b = loadClassData(name);
 *             return defineClass(name, b, 0, b.length);
 *         }
 *
 *         private byte[] loadClassData(String name) {
 *             // load the class data from the connection
 *              . . .
 *         }
 *     }
 * 
* *

"name">Binary names

* *

Any class name provided as a {@link String} parameter to methods in * ClassLoader must be a binary name as defined by * The Java™ Language Specification. * *

Examples of valid class names include: *

 *   "java.lang.String"
 *   "javax.swing.JSpinner$DefaultEditor"
 *   "java.security.KeyStore$Builder$FileBuilder$1"
 *   "java.net.URLClassLoader$3$1"
 * 
* * @see #resolveClass(Class) * @since 1.0 */

大致讲解了类加载的作用。和双亲委派模型
However, some classes may not originate from a file; they may originate from other sources, such as the network, or they could be constructed by an application.

ClassLoader 是一个抽象的类。正常情况下我们可以使用本地的类加载器完成。然而特殊情况下,我们需要加载网络上的类.

我们自定义

package com.danjiang.classloader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class NetClassLoader extends ClassLoader {

    private String rootUrl;

    public NetClassLoader(String rootUrl) {
        this.rootUrl = rootUrl;
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        byte[] classData = getClassData(name); //根据类的二进制名称,获得该class文件的字节码数组  
        if (classData == null) {  
            throw new ClassNotFoundException();  
        }  
        clazz = defineClass(name, classData, 0, classData.length);  //将class的字节码数组转换成Class类的实例
        return clazz;
    }

    /**
     * 根据类的二进制名称,获得该class文件的字节码数组 
     * @param name
     * @return
     */
    private byte[] getClassData(String name) {
        InputStream is = null;
        try {
            String path = classNameToPath(name);
            URL url = new URL(path);
            byte[] buff = new byte[1024 * 4];
            int len = -1;
            is = url.openStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((len = is.read(buff)) != -1) {
                baos.write(buff, 0, len);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    private String classNameToPath(String name) {
        return rootUrl + "/" + name.replace(".", "/") + ".class";
    }

}

运行类

package com.danjiang.classloader;

import java.lang.reflect.Method;

public class MainTest {
    public static void main(String[] args) {

        try {                   
            String rootUrl = "http://localhost:8080/test/";  
            NetClassLoader networkClassLoader = new NetClassLoader(rootUrl);  
            String classname = "Student";  

            Class clazz = networkClassLoader.loadClass(classname);  
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                String name = method.getName();
                 System.out.println(name);  
                 Object newInstance = clazz.newInstance();
                 if(name.equals("test")){
                     method.invoke(newInstance);
                 }
            }
            System.out.println(clazz.getClassLoader());  
            System.out.println(clazz.getSimpleName());  

        } catch (Exception e) {  
            e.printStackTrace();  
        }  


    }
}

我们在本地启动Tomact 并把编译的Student.class文件放在指定地址下;

E:\apache-tomcat-8.5.8\webapps\ROOT\test\Student.class

通过反射 调用Stuent的方法 执行结果如下

toString
setName
test
this    loader==com.danjiang.classloader.NetClassLoader@33909752
partent loader==sun.misc.Launcher$AppClassLoader@73d16e93
partent loader==sun.misc.Launcher$ExtClassLoader@75b84c92
partent loader==null
wait
wait
wait
equals
hashCode
getClass
notify
notifyAll
com.danjiang.classloader.NetClassLoader@33909752
Student

Student的代码


public class Student {

        private String name;

        public void setName(String name) {
            this.name = name;
        }


        public String toString(){
            return "Student :" + "name=" + name;
        }

        public void test() {
            Student stu = new Student();
            //stu.setName("me in C:\\Program Files\\Java\\jdk1.8.0_101\\jre\\lib\\ext");
            stu.setName("me in E:\\javaText");
            ClassLoader loader = Student.class.getClassLoader();
            System.out.println("this    loader==" + loader);
            while(loader != null) {
                loader = loader.getParent();
                System.out.println("partent loader==" + loader);
            }

        }
}

你可能感兴趣的:(Java)