在Java环境中,有个概念叫类加载器。(ClassLoader)就是先加载后使用。我们每使用一个对象,则先有类,类生对象。我们要使用类,则先将类加载其中。加载的是啥?加载的是字节码。我们开发的流程时。一般先定义一个类,譬如 Student.Java 后缀名为.java的一个文件。
public class Student{
private String name;
public void setName(String name) {
this.name = name;
}
}
编译。 命令用javac, Eclipse AndroidStudio会自动帮我们编译。
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.这也验证了双亲委派模型。
执行结果
loader 为空 默认指 Bootstrap ClassLoader :
Eclipse可以这样配置同样的效果
类加载机制做了个简单的说明。
我们来看一下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 tt>是一个抽象类。 鉴于类的"#name">二进制名称,类加载器应尝试查找或生成构成类定义的数据。 典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
*
* Every {@link Class Class} object contains a {@link
* Class#getClassLoader() reference} to the ClassLoader that defined
* it.
每个{@link Class Class tt>}对象都包含一个{@link Class# getClassLoader()引用}定义它的 ClassLoader tt>。
*
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 tt>的子类,以便扩展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 tt>类使用委派模型来搜索类和资源。 ClassLoader tt>的每个实例都有一个关联的父类加载器。 当请求查找类或资源时,在尝试查找类或资源本身之前, ClassLoader tt>实例将委派对类或资源的搜索到其父类加载器。 虚拟机的内置类加载器(称为“引导类加载器”)本身不具有父级,但可以作为 ClassLoader tt>实例的父级。
*
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 tt>环境变量定义的目录加载类。
----------------------------------------------------------
这里 这里 这里
*
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 tt>}方法。
*
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 tt>}创建一个类实例。 示例实现是:
*
* 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);
}
}
}