javaee论坛

普通会员

225648

帖子

352

回复

366

积分

楼主
发表于 2017-08-24 20:34:36 | 查看: 567 | 回复: 2
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";    }}
  • 63
  • 64

运行类

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的方法 执行结果如下

toStringsetNametestthis    loader==com.danjiang.classloader.NetClassLoader@33909752partent loader==sun.misc.Launcher$AppClassLoader@73d16e93partent loader==sun.misc.Launcher$ExtClassLoader@75b84c92partent loader==nullwaitwaitwaitequalshashCodegetClassnotifynotifyAllcom.danjiang.classloader.NetClassLoader@33909752Student

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

帖子

331

回复

337

积分
沙发
发表于 2022-04-23 09:42:48

围观

普通会员

0

帖子

339

回复

346

积分
板凳
发表于 2023-09-24 20:49:26

很好

您需要登录后才可以回帖 登录 | 立即注册

触屏版| 电脑版

技术支持 历史网 V2.0 © 2016-2017