Java虚拟机中定义了一个类装载器子系统和执行引擎子系统.其中类装载器子系统主要是负责装载类二进制文件到JVM中的,而执行引擎子系统则负责执行其中的指令.二者都是虚拟机的具体实现,执行引擎程序代码中无法控制,相对来说类装载器就比较灵活.JVM规范中定义虚拟机必须实现启动类装载器(bootstarp),但是用户可以定义自己的类装载器.一个程序启动的时候有3个类加载器.
第一个是启动类加载器,它负责加载Java的核心类.它是JVM实现的一部分,不是ClassLoader的子类.是用C代码实现的.第二个类加载器是扩展类加载器,它负责加载JDK的扩展类,也就是目录配置属性.第三个是APP的类加载器,通常用ClassLoader.getSystemClassLoader()可以获得,负责加载CLASSPATH下的类.一般这3个类加载器足以满足我们的应用.虽然假如我们的程序需要加载上述3个加载器不能到达类.那么我们就只能定义自己的类加载器.
- public static void main(String[] args) throws Exception
- {
- try
- {
- Class.forName("org.apache.commons.lang.StringUtils");
- }
- catch(Exception e)
- {
- e.printStackTrace();
- }
- File file = new File("D:"+File.separator+"commons-lang-2.5.jar");
- URL url = file.toURI().toURL();
- URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
- Class clazz = urlClassLoader.loadClass("org.apache.commons.lang.StringUtils");
- Method isEmpty = clazz.getMethod("isEmpty", String.class);
- System.out.println(isEmpty.invoke(null,"This is not empty!"));
- System.out.println(isEmpty.invoke(null,""));
- }
假设在D盘放置了一个apache的commons-lang包,想调用其中的方法,但是我们没有把他引入到classpath.所以在上述的3个ClassLoader中根本就找不到该类的存在.如上述代码用Class.forName方法加载类,此时使用的是AppClassLoader,它的上层ClassLoader也没有该类的引用.所以自然会报出java.lang.ClassNotFoundException的异常出来.所以我们必须定义自己的ClassLoader去加载它,这时候URLClassLoader 就派上用场了.它接受一个URL数组为参数,代表的意义是它可以在提供的路径中加载到提供的类.于是上述代码就可以使用loadClass来加载返回一个Class对象.然后用java反射来调用加载的类的方法.上述代码是调用了org.apache.commons.lang.StringUtils的静态方法isEmpty.
在WEB容器的实现中也用了此方法.有兴趣的话可以参考下Tomcat的源码.容器启动的时候Bootstrap在设置完基本的目录之后,第一件事就是先调用initClassLoaders来构造自己的类加载器层次.