java1.1中的实现
java1.1的类加载机制相对单一,而用户自定义加载器的重写却比较复杂。。
主要需要重写ClassLoader中一个方法:Class loadClass(String name)。
Class loadClass(String name):loadClass(String name)(或者loadClass(String name , boolean resolve))这个方法是加载的核心,他将根据类名(全名,比如java.lang.String)获得对应类的二进制数据,然后通过Class defineClass(byte[] b)将二进制数据加载到JVM的方法区,defineClass返回对应类的Class 实例,并根据可选的参数resolve决定是否需要现在解析这个类,之后将这个Class实例作为 loadClass方法的返回值。
其中,若是没有办法实现加载和defineClass,即无法通过本加载器直接加载类的情况,则使用Class findSystemClass(String name)委派系统的加载器查找,如果能找到则加载,否则,抛出ClassNotFoundException。可见,这个方法必须放在 loadClass方法中自定义加载的代码之后。
重写loadClass方法还是相对复杂的,需要考虑到各种加载情况。
以下的例子实现了这一过程。
它是一个能直接由.java源文件实现类加载的 加载器:CompilingClassLoader。
就像之前说到的,只需要重写loadClass()方法,用户自定义的加载实现逻辑都在这个方法中。至于代码中的其中的
其他的方法,比如defineClass(String name),findSystemClass(String name)都是由loadClass调用的。
- /*
- 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;
- }
- <span style="font-size: 16px; ">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 );
- }
- }
- {
- static public void main( String args[] ) throws Exception {
- System.out.println( "foo! "+args[0]+" "+args[1] );
- }
- }
- java CCLRun Foo arg1 arg2
- CCL: Compiling Foo.java...
- foo! arg1 arg2
来自IBM DeveloperWorks : Understanding the Java ClassLoader
java1.2以后的实现
改版以后,ClassLoader实现了parent-child模型,更好的控制安全性方面的问题。
- |
- (not-found?) (by findLoadedClass)
- |
- B.loadClass()
- |
- (not found?) (by findLoadedClass)
- |
- systemclassloader.loadClass (Bs parent, also can be
- | called classpath classloader)
- |
- (not found?) (by findLoadedClass)
- |
- ootstrap classloader.loadClass (the bootstrap classloader,
- | (this has no parent)
- |
- (not found?)
- |
- systemclassloader.findClass (on system classloader,
- | will try to "find" class in "classpath")
- |
- (not found?) ClassNotFoundException
- |
- B.findClass
- |
- (not found?) ClassNotFoundException
- |
- A.findClass
- |
- (not found?)
- |
- ClassNotFoundException
- if (parent != null) {
- c = parent.loadClass(name, false);
- } else {
- c = findBootstrapClassOrNull(name);
- }
- } catch (ClassNotFoundException e) {
- // ClassNotFoundException thrown if class not found
- // from the non-null parent class loader
- }
- import java.io.*;
- import org.objectweb.asm.ClassReader;
- import org.objectweb.asm.ClassWriter;
- import biaobiaoqi.asm.*;
- public class ASMClassLoader extends ClassLoader
- {
- String basePath ;
- /**reference to System Classloader as the parent class loader
- * @param path <br> the path of .class files will be loaded
- */
- public ASMClassLoader(String path){
- basePath = path ;
- }
- /**
- * reference to parent as it's parent classloader
- * @param path
- * @param parent
- */
- public ASMClassLoader(String path , ClassLoader parent){
- super(parent);
- basePath = path ;
- }
- @Override
- public Class findClass(String name) throws ClassNotFoundException{
- System.out.println("findClass");
- byte[] raw;
- try {
- raw = getBytesFromBasePath( name );
- } catch (IOException e) {
- e.printStackTrace();
- throw new ClassNotFoundException();
- }
- byte[] transformed = instrumentBtyeCode(raw);
- /*
- try{
- FileOutputStream file = new FileOutputStream( "/home/biaobiaoqi/" +name.replace( '.', '/' )+".class");
- file.write( transformed);
- file.close();
- }
- catch (IOException e) {
- e.printStackTrace();
- }
- */
- if (transformed == null){
- throw new ClassNotFoundException();
- }
- return defineClass(name, transformed, 0, transformed.length );
- }
- private byte[] getBytesFromBasePath( String className ) throws IOException ,ClassNotFoundException{
- String fileStub = className.replace( '.', '/' );
- String classFileName = basePath +fileStub+".class";
- File file = new File( classFileName );
- long len = file.length();
- byte raw[] = new byte[(int)len];
- FileInputStream fin = new FileInputStream( file );
- int r = fin.read( raw );
- if (r != len)
- throw new IOException( "Can't read all, "+r+" != "+len );
- fin.close();
- return raw;
- }
- private byte[] instrumentBtyeCode(byte[] raw){
- ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
- ASMClassAdapter mca = new ASMClassAdapter(cw);
- ClassReader cr = new ClassReader(raw);
- cr.accept(mca, 0);
- return cw.toByteArray();
- }
- @Override
- public Class loadClass( String name, boolean resolve )
- throws ClassNotFoundException {
- System.out.println("loadClass_resolve");
- return super.loadClass(name ,resolve);
- }
- @Override
- public Class loadClass( String name )
- throws ClassNotFoundException {
- System.out.println("loadClass");
- return super.loadClass(name );
- }
- }
温馨提示:不要把需要用自定义加载器加载的类文件直接放在system classloader能达到的路径下,否则,parent-first会帮你加载好那个类。特别是在用eclipse等IDE开发的时候。。这个bug让我花了很长时间。。