1,类的加载
每个开发人员对java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这背后就涉及到了java技术体系中的类加载。Java的类加载机制是技术体系中比较核心的部分,虽然和大部分开发人员直接打交道不多,但是对其背后的机理有一定理解有助于排查程序中出现的类加载失败等技术问题,对理解java虚拟机的连接模型和java语言的动态性都有很大帮助。
那么什么是类的加载?
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
类加载器是Java语言的一个创新,也是Java语言流行的重要原因之一。它使得Java类可以被动态加载到Java虚拟机中并执行。类加载器从JDK1.0就出现了,最初是为了满足JavaApplet的需要而开发出来的。JavaApplet需要从远程下载Java类文件到浏览器中并执行。现在类加载器在Web容器和OSGi中得到了广泛的使用,而类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
2,类的生命周期
类加载的过程中包括有加载,验证,准备,解析,初始化五个阶段。而需要注意的是在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
加载:查找并加载类的二进制数据
加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:
1、通过一个类的全限定名来获取其定义的二进制字节流。(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等)
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,电脑培训http://www.kmbdqn.cn/发现因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
1.将你这段字符串输出到一个文件里,用Java类文件的方式命名。2.调用外部javac命令将该文件编译。
3.用类加载器(ClassLoad)动态加载新的class文件并用Class.forName()注册该类,然后就可以正常使用了。
上面的每一步都能在baidu中找到实现方法,自己发挥吧。
设我们有一个hello.jar文件,里面有一个Util类,我们希望在运行期调将这个jar包放入到我们运行环境并且调用里面的Util.getVersion方法。怎么实现?在java中,我们的类都是通过ClassLoader来加载的,同时ClassLoader具有层级关系,当某个类找不到时,它会去他的父类加载器去寻找,如果依然找不到,就抛出ClassNotFoundException了。
为了动态加载hello.jar里面的Util类,我们需要将这个jar包放入到我们的类加载器中去,然后再获取里面的类。如下面的代码。
// 位于hello.jar
package com.flyingzl
public class Util {
public static void getVersion(){
System.out.println("java version: " + System.getProperty("java.version"))
}
}
import java.io.File
import java.lang.reflect.Method
import java.net.URL
import java.net.URLClassLoader
public class Main {
public static void main(String[] args) {
URL[] urls = new URL[] {}
MyClassLoader classLoader = new MyClassLoader(urls, null)
try {
classLoader.addJar(new File("c:/hello.jar").toURI().toURL())
Class<?>clazz = classLoader.loadClass("com.flyingzl.Util")
Method method = clazz.getDeclaredMethod("getVersion")
method.invoke(null)
classLoader.close()
} catch (Exception e) {
e.printStackTrace()
}
}
static class MyClassLoader extends URLClassLoader {
public MyClassLoader(URL[] urls) {
super(urls)
}
public MyClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent)
}
public void addJar(URL url) {
this.addURL(url)
}
}
}
注意:这里仅仅是为了展示如何动态加载jar包,代码写得很粗,生产代码需要有更完善的异常处理。我们只关心如何动态加载jar包即可。
动态加载jar包,需要用到java.net.URLClassLoader这个类,它可以指定一个路径将jar包或者classes文件加载到类空间。加载完毕后,直接调用loadClass就可以加载指定的类,然后通过反射生成实例或者调用方法即可。
其实,Tomcat等服务器也利用了此思路,比如每一个web应用启动时,它都会自动加载其下的lib文件夹下的jar包。
运行程序,我们就可以看到程序正常输出:
java version: 1.7.0_03