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 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 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) { * * . . . * } * } * </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); if (classData == null) { throw new ClassNotFoundException(); } clazz = defineClass(name, classData, 0, classData.length); 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.classloaderimport java.lang.reflect.Methodpublic 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 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); } }}