类加载机制及自定义ClassLoader(另一博客)

xiaoxiao2021-02-28  93

在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 = new NetworkClassLoader(host, port); * Object main = loader.loadClass("Main", true).newInstance(); *  . . . * </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 *  . . . * } * } * </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™ 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"; } } 6364

运行类

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); } } }
转载请注明原文地址: https://www.6miu.com/read-32953.html

最新回复(0)