类加载机制ClassLoader

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

public class Student{

    private String name;

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

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

这里写图片描述
生成 Student.class文件
这里写图片描述

Student.class类文件用文本打开 是一段二进制字节码
这里写图片描述

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

如果我们将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)上所指定的类库,开发者可直接使用。
这里写图片描述
如果父类已经加载了此类,则子类就没有必要加载。同时也是为安全方面考虑。譬如我们自定义了一个java.lang.String类。如果加载了我们自己的类那么危害很大。双亲委派正是解决这样的问题。

我们做个试验。我们编译一个Student.java类成Student.class.再打成TestExtensionClassLoader.jar文件。并放入Jdk1.8.0/jre/lib/ext下
这里写图片描述
这里写图片描述
这里写图片描述
然后我们修改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是AppClassLoader.这也验证了双亲委派模型。

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

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

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

这里写图片描述

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

/**
 * A class loader is an object that is responsible for loading classes. The
 * class <tt>ClassLoader</tt> is an abstract class.  Given the <a
 * href="#name">binary name</a> 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.
 一个类装载器是一个负责加载类的对象。 类<tt> ClassLoader </ tt>是一个抽象类。 鉴于类的<a href="#name">二进制名称</a>,类加载器应尝试查找或生成构成类定义的数据。 典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
 *
 * <p> Every {@link Class <tt>Class</tt>} object contains a {@link
 * Class#getClassLoader() reference} to the <tt>ClassLoader</tt> that defined
 * it.
 每个{@link Class <tt> Class </ tt>}对象都包含一个{@link Class# getClassLoader()引用}定义它的<tt> ClassLoader </ tt>。
 * <p> <tt>Class</tt> 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.
数组类的<tt>类对象不是由类加载器创建的,而是根据Java运行时的需要自动创建。由{@link Class#getClassLoader()}返回的数组类的类加载器 与其类型的类加载器相同; 如果元素类型是原始类型,则数组类没有类加载器。
 * <p> Applications implement subclasses of <tt>ClassLoader</tt> in order to
 * extend the manner in which the Java virtual machine dynamically loads
 * classes.
  应用程序实现<tt> ClassLoader </ tt>的子类,以便扩展Java虚拟机动态加载类的进程。
 * <p> Class loaders may typically be used by security managers to indicate
 * security domains.
 * 安全管理员通常可以使用类加载器来指示安全域。 *
 * <p> The <tt>ClassLoader</tt> class uses a delegation model to search for
 * classes and resources.  Each instance of <tt>ClassLoader</tt> has an
 * associated parent class loader.  When requested to find a class or
 * resource, a <tt>ClassLoader</tt> 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 <tt>ClassLoader</tt> instance.
<tt> ClassLoader </ tt>类使用委派模型来搜索类和资源。 <tt> ClassLoader </ tt>的每个实例都有一个关联的父类加载器。 当请求查找类或资源时,在尝试查找类或资源本身之前,<tt> ClassLoader </ tt>实例将委派对类或资源的搜索到其父类加载器。 虚拟机的内置类加载器(称为“引导类加载器”)本身不具有父级,但可以作为<tt> ClassLoader </ tt>实例的父级。
 * <p> Class loaders that support concurrent loading of classes are known as
 * <em>parallel capable</em> class loaders and are required to register
 * themselves at their class initialization time by invoking the
 * {@link
 * #registerAsParallelCapable <tt>ClassLoader.registerAsParallelCapable</tt>}
 * method. Note that the <tt>ClassLoader</tt> class is registered as parallel
 * capable by default. However, its subclasses still need to register themselves
 * if they are parallel capable. <br>
 * 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
 * <tt>loadClass</tt>} methods).

 * <p> 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
 * <tt>CLASSPATH</tt> environment variable.
通常,Java虚拟机以平台相关的方式从本地文件系统加载类。 例如,在UNIX系统上,虚拟机从由<tt> CLASSPATH </ tt>环境变量定义的目录加载类。
----------------------------------------------------------
   这里 这里 这里
 * <p> 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)
 * <tt>defineClass</tt>} converts an array of bytes into an instance of class
 * <tt>Class</tt>. Instances of this newly defined class can be created using
 * {@link Class#newInstance <tt>Class.newInstance</tt>}.
然而,一些类可能不是源于一个文件; 它们可以来自诸如网络的其他来源,或者它们可以由应用构建。 方法{@link #defineClass(String,byte [],int,int) defineClass }将一个字节数组转换为类Class 的实例。 这个新定义的类的实例可以使用{@link Class#newInstance  Class.newInstance }创建。

 * <p> 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 <tt>loadClass</tt>} method of
 * the class loader that originally created the class.
类加载器创建的对象的方法和构造函数可以引用其他类。 要确定所引用的类,Java虚拟机调用最初创建该类的类加载器的{@link #loadClass <tt> loadClass </ tt>}方法。
 * <p> For example, an application could create a network class loader to
 * download class files from a server.  Sample code might look like:
例如,应用程序可以创建一个网络类加载器来从服务器下载类文件。 示例代码可能如下所示:
 * <blockquote><pre>
 *   ClassLoader loader&nbsp;= new NetworkClassLoader(host,&nbsp;port);
 *   Object main&nbsp;= loader.loadClass("Main", true).newInstance();
 *       &nbsp;.&nbsp;.&nbsp;.
 * </pre></blockquote>
 *
 * <p> The network class loader subclass must define the methods {@link
 * #findClass <tt>findClass</tt>} and <tt>loadClassData</tt> 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 <tt>defineClass</tt>} to
 * create a class instance.  A sample implementation is:
网络类加载器子类必须定义方法{@link #findClass <tt>findClass 和loadClassData 以从网络加载类。 
一旦下载构成类的字节,它应该使用方法{@link #defineClass <tt> defineClass </ tt>}创建一个类实例。 示例实现是:
 * <blockquote><pre>
 *     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
 *             &nbsp;.&nbsp;.&nbsp;.
 *         }
 *     }
 * </pre></blockquote>
 *
 * <h3> <a name="name">Binary names</a> </h3>
 *
 * <p> Any class name provided as a {@link String} parameter to methods in
 * <tt>ClassLoader</tt> must be a binary name as defined by
 * <cite>The Java&trade; Language Specification</cite>.
 *
 * <p> Examples of valid class names include:
 * <blockquote><pre>
 *   "java.lang.String"
 *   "javax.swing.JSpinner$DefaultEditor"
 *   "java.security.KeyStore$Builder$FileBuilder$1"
 *   "java.net.URLClassLoader$3$1"
 * </pre></blockquote>
 *
 * @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);
            }

        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值