Java ClassLoader是java运行系统中一个相当重要可是常常被忽略的组件。它负责在运行时寻找并加载类文件。建立自定义的ClassLoader能够完全重定义如何将类文件加载至系统。java
这个教程对Java的ClassLoader进行整体概述,并给了一个自定义ClassLoader的例子。这个ClassLoader会在加载代码以前自动编译。你将会了解ClassLoader是作什么的,以及如何建立自定义ClassLoader。面试
本教程须要阅读者对Java编程有基础了解,包括建立,编译和执行简单的命令行Java程序。编程
阅读完本教程以后,你会知道如何:数组
在全部的编程语言中,Java以运行在Java虚拟机上而独树一帜。这意味着编译的程序将以一种独特的,与平台无关的形式运行在目标机器上,而不是目标机器的格式。这种格式在不少方面和传统的可执行程序相比,有很大的区别。浏览器
Java程序与C或C++程序最大的不一样在于,它不是单个可执行文件,而是由许多单独的类文件构成,每一个类文件对应一个Java类。缓存
不只如此,这些类文件并非一次性加载到内存的,而是按需加载的。ClassLoader是JVM的一部分,它将类加载到内存中。安全
此外,Java ClassLoader是用Java编写的。这意味着能够轻松的建立本身的ClassLoader,无需了解JVM更多的细节。服务器
若是JVM已经有一个ClassLoader了,为何还要再写一个?好问题,默认的ClassLoader只知道如何从本地的文件系统中加载类文件。通常场景下,当你在本地编写代码而且在本地编译时,彻底足够了。微信
可是,JAVA语言最新颖的特色之一就是能够从本地硬盘或是互联网以外的地方获取类。好比,浏览器使用自定义的ClassLoader从网站上获取可执行内容。网络
还有不少其它获取类文件的方法。除了从本地或是网上加载类文件,还能够用类加载器来:
任何生成Java字节码的内容均可以集成到你的应用程序中去。
若是你曾经使用过applet,你确定用到了一个自定义的类加载器。
在Sun发布Java语言的时候,最使人兴奋的事情之一就是观察这项技术是如何执行从远程Web服务器及时加载代码的。它们是经过来自远程的Web服务器的HTTP链接发送字节码并在本地运行,这一点使人兴奋。
Java语言支持自定义ClassLoader的功能使这一想法成为可能。applet中有一个自定义的ClassLoader,它不是从本地文件系统加载类文件,而是从远程Web服务器上获取,经过Http加载原始字节码,再在jvm中转化为类。
浏览器和Applet中的类加载器还有别的功能:安全管理,防止不一样页面上的applet相互影响等。
下面咱们将会建立一个自定义的类加载器叫作CompilingClassLoader(CCL)
、CCL会帮咱们编译Java代码。它基本上就像是在运行系统中直接构建一个简单的make程序。
ClassLoader的基本目的是为类的请求提供服务。JVM须要一个类,因而它经过类的名字询问ClassLoader来加载这个类。ClassLoader试着返回一个表明该类的对象。
经过覆盖此过程不一样阶段对应的方法,能够建立自定义的ClassLoader。
在本文的剩余部分,你会了解到ClassLoader中的一些关键方法。你会了解到每一个方法的用途以及它在类加载过程当中是如何调用的。你还会了解当你在自定义ClassLoader时须要完成的工做。
loadClass
方法##、ClassLoader.loadClass()
方法是ClassLoader的入口。它的方法标签以下:
Class loadClass(String name, boolean resolve)
name
参数表明JVM须要的类的名称,好比Foo
或是java.lang.Object
。
resolve
参数说明类是否须要被解析。能够把类的解析理解为彻底的准备好执行类。解析并非必要的。若是JVM只须要肯定该类存在或是找出其父类,则无需解析。
在java1.1版本之前,自定义ClassLoader只须要重写loadClass
方法。
defineClass
方法是整个ClassLoader的核心。此方法将原始字节数组转化为一个Class
对象。原始字节数组包含从本地或是远程获得的数据。
defineClass
负责处理JVM的许多复杂,神秘并且依赖于具体实现的部分。它将字节码解析为运行时的数据结构,检查其有效性等。不用担忧,这些你不用本身实现。事实上,你根本无法重写它,由于该方法为final方法。
findSystemClass方法
findSysetmClass
方法从本地文件系统中加载文件。它在本地文件系统中查找类文件,若是存在,使用defineClass
将其从原始字节转化为类对象。这是JVM在运行Java应用程序时加载类的默认机制。
对于自定义的ClassLoader,咱们只会在尝试了别的方法来加载类内容以后,才调用findSystemClass
方法。道理很简单:自定义的ClassLoader包含加载特殊类的一些步骤,可是并不是全部的类都是特殊类。好比,即使ClassLoader须要从远程网站上获取一些类,仍是有许多类须要从本地的Java库中加载。这些类并非咱们关注的重点,所以咱们须要JVM用默认的方式来获取。
整个流程以下:
findSystemClass
从文件系统中加载出来。在大多数自定义的ClassLoader中,你须要先滴啊用findSystemClass
来减小对远程网站的访问,由于大多数Java类都位于本地的类库中。可是,在下一节中你会看到,在自动将应用代码编译以前,咱们不但愿JVM从本地文件系统加载类。
resolveClass
方法如前文所说,类的加载是能够部分进行(不进行解析)或是完全进行的(进行解析)。当咱们实现本身的loadClass
方法时,咱们或许须要调用resolveClass
方法,这取决于loadClass
中的resolve
参数的值。
findLoadedClass
方法findLoadedClass
方法充当一个缓存调用机制:当loadClass
方法被调用时,他会调用这个方法来查看类是否已经被加载过了,省去了重复加载。这个方法应当最早被调用。
咱们的例子中loadClass
执行如下几步(这里咱们不会特别关注到底采用了什么神奇的方法来获取类文件。它能够是从本地,网络或者是压缩文件中得到的,总之咱们得到了原始类文件的字节码):
findLoadedClass
查看是否已经加载过该类defineClass
将其转化为Class
对象findSystemClass
,看是否能从本地文件系统得到类resolve
值为true,则调用resolveClass
来解析Class
对象ClassNotFoundException
CompilingClassLoader
CCL
的做用是确保代码已经被编译,而且是最新版本的。
如下是该类的描述:
ClassNotFoundException
findSystemClass
看是否有用ClassNotFoundException
在深刻研究以前,咱们应该回过头来看一下Java的编译机制。总的来讲,当你请求一个类的时候,Java不仅是编译各类类信息,它还编译了别的相关联的类。
CCL会按需一个接一个的编译相关的类。可是,当CCL编译完一个类以后试着去编译其它相关类的时候会发现,其它的类已经编译完成了。为何呢?Java编译器遵循一个规则:若是一个类不存在,或者它相对于源码已通过时了,就须要编译它。从本质上讲,Java编译器先CCL一步完成了大部分的工做。
CCL在编译类的时候会打印其编译的应用程序。在大多数场景里面,你会看到它在程序的主类上调用编译器。
可是,有一种状况是不会在第一次调用时编译全部类的的。若是你经过类名Class.forNasme
加载一个类,Java编译器不知道该类须要哪些信息。在这种场景下,你会看到CCL会再次运行Java编译器。
CompilingClassLoader
为了使用CCL,咱们须要用一种独特的方式启动程序。正常的启动程序以下:
% java Foo arg1 arg2
而咱们启动方式以下:
% java CCLRun Foo arg1 arg2
CCLRun是一个特殊的桩程序,它会建立一个CompilingClassLoader并使用它来加载程序的main方法,确保整个程序的类会经过CompilingClassLoader加载。CCLRun使用Java反射API来调用main方法并传参
Java1.2之后ClassLoader有一些变更。原有版本的ClassLoader仍是兼容的,并且在新版本下开发ClassLoader更容易了
新的版本下采用了delegate模型。ClassLoader能够将类的请求委托给父类。默认的实现会先调用父类的实现,在本身加载。可是这种模式是能够改变的。全部的ClassLoader的根节点是系统ClassLoader。它默认会从文件系统中加载类。
loadClass
默认实现一个自定义的loadClass
方法一般会尝试用各类方法来得到一个类的信息。若是你写了大量的ClassLoader,你会发现基本上是在重复写复杂而变化不大的代码。
java1.2的loadClass
的默认实现中容许你直接重写findClass
方法,loadClass
将会在合适的时候调用该方法。
这种方式的好处在于你无须重写loadClass
方法。
findClass
该方法会被loadClass
的默认实现调用。findClass
是为了包含ClassLoader全部特定的代码,而无需写大量重负的其余代码
getSystenClassLoader
不管你是否重写了findClass
或是loadClass
方法,getSystemClassLoader
容许你直接得到系统的ClassLoader(而不是隐式的用findSystemClass
得到)
getParent
该方法容许类加载器获取其父类加载器,从而将请求委托给它。当你自定义的加载器没法找到类时,可使用该方法。父类加载器是指包含建立该类加载代码的加载器。
// $Id$ import java.io.*; /* A CompilingClassLoader compiles your Java source on-the-fly. It checks for nonexistent .class files, or .class files that are older than their corresponding source code. */ public class CompilingClassLoader extends ClassLoader { // Given a filename, read the entirety of that file from disk // and return it as a byte array. private byte[] getBytes( String filename ) throws IOException { // Find out the length of the file File file = new File( filename ); long len = file.length(); // Create an array that's just the right size for the file's // contents byte raw[] = new byte[(int)len]; // Open the file FileInputStream fin = new FileInputStream( file ); // Read all of it into the array; if we don't get all, // then it's an error. int r = fin.read( raw ); if (r != len) throw new IOException( "Can't read all, "+r+" != "+len ); // Don't forget to close the file! fin.close(); // And finally return the file contents as an array return raw; } // Spawn a process to compile the java source code file // specified in the 'javaFile' parameter. Return a true if // the compilation worked, false otherwise. private boolean compile( String javaFile ) throws IOException { // Let the user know what's going on System.out.println( "CCL: Compiling "+javaFile+"..." ); // Start up the compiler Process p = Runtime.getRuntime().exec( "javac "+javaFile ); // Wait for it to finish running try { p.waitFor(); } catch( InterruptedException ie ) { System.out.println( ie ); } // Check the return code, in case of a compilation error int ret = p.exitValue(); // Tell whether the compilation worked return ret==0; } // The heart of the ClassLoader -- automatically compile // source as necessary when looking for class files public Class loadClass( String name, boolean resolve ) throws ClassNotFoundException { // Our goal is to get a Class object Class clas = null; // First, see if we've already dealt with this one clas = findLoadedClass( name ); //System.out.println( "findLoadedClass: "+clas ); // Create a pathname from the class name // E.g. java.lang.Object => java/lang/Object String fileStub = name.replace( '.', '/' ); // Build objects pointing to the source code (.java) and object // code (.class) String javaFilename = fileStub+".java"; String classFilename = fileStub+".class"; File javaFile = new File( javaFilename ); File classFile = new File( classFilename ); //System.out.println( "j "+javaFile.lastModified()+" c "+ // classFile.lastModified() ); // First, see if we want to try compiling. We do if (a) there // is source code, and either (b0) there is no object code, // or (b1) there is object code, but it's older than the source if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { // Try to compile it. If this doesn't work, then // we must declare failure. (It's not good enough to use // and already-existing, but out-of-date, classfile) if (!compile( javaFilename ) || !classFile.exists()) { throw new ClassNotFoundException( "Compile failed: "+javaFilename ); } } catch( IOException ie ) { // Another place where we might come to if we fail // to compile throw new ClassNotFoundException( ie.toString() ); } } // Let's try to load up the raw bytes, assuming they were // properly compiled, or didn't need to be compiled try { // read the bytes byte raw[] = getBytes( classFilename ); // try to turn them into a class clas = defineClass( name, raw, 0, raw.length ); } catch( IOException ie ) { // This is not a failure! If we reach here, it might // mean that we are dealing with a class in a library, // such as java.lang.Object } //System.out.println( "defineClass: "+clas ); // Maybe the class is in a library -- try loading // the normal way if (clas==null) { clas = findSystemClass( name ); } //System.out.println( "findSystemClass: "+clas ); // Resolve the class, if any, but only if the "resolve" // flag is set to true if (resolve && clas != null) resolveClass( clas ); // If we still don't have a class, it's an error if (clas == null) throw new ClassNotFoundException( name ); // Otherwise, return the class return clas; } }
import java.lang.reflect.*; /* CCLRun executes a Java program by loading it through a CompilingClassLoader. */ public class CCLRun { static public void main( String args[] ) throws Exception { // The first argument is the Java program (class) the user // wants to run String progClass = args[0]; // And the arguments to that program are just // arguments 1..n, so separate those out into // their own array String progArgs[] = new String[args.length-1]; System.arraycopy( args, 1, progArgs, 0, progArgs.length ); // Create a CompilingClassLoader CompilingClassLoader ccl = new CompilingClassLoader(); // Load the main class through our CCL Class clas = ccl.loadClass( progClass ); // Use reflection to call its main() method, and to // pass the arguments in. // Get a class representing the type of the main method's argument Class mainArgType[] = { (new String[0]).getClass() }; // Find the standard main method in the class Method main = clas.getMethod( "main", mainArgType ); // Create a list containing the arguments -- in this case, // an array of strings Object argsArray[] = { progArgs }; // Call the method main.invoke( null, argsArray ); } }
public class Foo { static public void main( String args[] ) throws Exception { System.out.println( "foo! "+args[0]+" "+args[1] ); new Bar( args[0], args[1] ); } }
import baz.*; public class Bar { public Bar( String a, String b ) { System.out.println( "bar! "+a+" "+b ); new Baz( a, b ); try { Class booClass = Class.forName( "Boo" ); Object boo = booClass.newInstance(); } catch( Exception e ) { e.printStackTrace(); } } }
package baz; public class Baz { public Baz( String a, String b ) { System.out.println( "baz! "+a+" "+b ); } }
public class Boo { public Boo() { System.out.println( "Boo!" ); } }
想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注个人微信公众号!将会不按期的发放福利哦~