论坛首页 入门技术论坛

自定义ClassLoader遇到的问题

浏览 4891 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-09-16  
1.自定义的类加载器

package classloader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
/**
 * 加载类----->定义类------>解析类
 * loadClass---->defineClass ------>resolveClass
 */
class MyClassLoader extends ClassLoader {
    
    public static void main(String[] args)throws Exception{
		
		 ClassLoader loader = new MyClassLoader();
		 Class clazz = loader.loadClass("classloader.Pig");
         Object pig = clazz.newInstance();
         System.out.println(pig.toString());
         System.out.println(clazz.getClassLoader());   
         System.out.println(pig);   
	}
    
    public MyClassLoader(){
    }
    
    /*  类加载器
     *  resolve:参数告诉方法是否需要解析类。在准备执行类之前,应考虑类解析。并不总是需要解析。如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。 
     */
    public Class loadClass( String name, boolean resolve ) throws ClassNotFoundException{
    	// 目标Class
        Class clas = null;

        // 看是否已经加载该类
        clas = findLoadedClass( name );
        
        if(clas == null){
        	clas = findClass(name);
        }
        
        //如果class对象不存在则在系统中查找
        if (clas==null) {
          //它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象
         //核心:使用的ClassLoader是系统默认的:sun.misc.Launcher$AppClassLoader@131f71a
          clas = findSystemClass( name );
        }
        
        if(clas == null){
        	
        	throw new ClassNotFoundException("该类不存在");
        }
        
        //是否需要解析该类
        if (resolve && clas != null)
            resolveClass( clas );
        return clas;
     }
    
    //构造该类的Class对象
    public Class findClass(String name){
    	
    	try{
            byte[] b = loadClassBytes();
            if(b == null){
            	return null;
            }
            return defineClass("classloader.Pig", b, 0, b.length);
            
        }catch(Exception e){
        	System.out.print(e.getMessage());
        }
        return null;
    }
    /**
     * 加载编译后字节码文件的数据
     */
    private byte[] loadClassBytes() throws  ClassNotFoundException {   
	try {
			String classFile = "c:/temp/Pig.class";
			FileInputStream fis = new FileInputStream(classFile);
			FileChannel fileC = fis.getChannel();
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			WritableByteChannel outC = Channels.newChannel(baos);
			ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
			while (true) {
				int i = fileC.read(buffer);
				if (i == 0 || i == -1) {
					break;
				}
				buffer.flip();
				outC.write(buffer);
				buffer.clear();
			}
			fis.close();
			return baos.toByteArray();
		} catch (IOException fnfe) {
			throw new ClassNotFoundException(className);
		}
	}

}

2.Pig类的代码如下:
package classloader;

public class Pig {

	public static void main(String[] args){
		System.out.println("OK");
	}
}


Pig.class文件拷贝到c:/temp目录下。

出现的问题:在自定义的MyClassLoader中return defineClass("classloader.Pig", b, 0, b.length);报异常:
Exception in thread "main" java.lang.ClassCircularityError: classloader/Pig
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(Unknown Source)
	at java.lang.ClassLoader.defineClass(Unknown Source)
	at classloader.MyClassLoader.findClass(MyClassLoader.java:69)
	at classloader.MyClassLoader.loadClass(MyClassLoader.java:40)
	at java.lang.ClassLoader.loadClass(Unknown Source)
	at java.lang.ClassLoader.loadClassInternal(Unknown Source)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(Unknown Source)
	at java.lang.ClassLoader.defineClass(Unknown Source)
	at classloader.MyClassLoader.findClass(MyClassLoader.java:69)
	at classloader.MyClassLoader.loadClass(MyClassLoader.java:40)
	at java.lang.ClassLoader.loadClass(Unknown Source)
	at classloader.MyClassLoader.main(MyClassLoader.java:19)


如果Pig.java不带包名(即去掉:package classloader;)。然后MyClassLoader里相应的包名也都去掉。就完全没问题。
   发表时间:2008-09-16  
已解决,是因为还需要loadClassPig的父类Object一次。
0 请登录后投票
   发表时间:2008-09-16  
引用
已解决,是因为还需要loadClassPig的父类Object一次。


由于你每次加载类都是加载Pig类,所以加载父类就认为循环继承了,所以抛出ClassCircularityError。

关键是为什么会加载2次?也就是说第一次调用完defineClass("Pig")以后,虚拟机会再次使用Pig的loader去加载Pig父类,我找了下Vm spec,有这么一段说明:

引用
5.3 Creation and Loading
Creation of a class or interface C denoted by the name N consists of the construction in the method area of the Java virtual machine (§3.5.4) of an implementation-specific internal representation of C. Class or interface creation is triggered by another class or interface D, which references C through its runtime constant pool. Class or interface creation may also be triggered by D invoking methods in certain Java class libraries (§3.12) such as reflection.


引用
The Java virtual machine uses one of three procedures to create class or interface C denoted by N:


If N denotes a nonarray class or an interface, one of the two following methods is used to load and thereby create C :

If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C (§5.3.1).

If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).


这应该很说明问题了,至于下午你说不含包名的那个为什么不报错,是由于你加载文件并不是固定加载Pig,所以不会让虚拟机以为循环继承.
0 请登录后投票
   发表时间:2008-09-17  
多谢大兵瑞恩(leadyu)指点

为什么调用完defineClass("Pig")以后,虚拟机会再次使用Pig的loader去加载Pig父类?

有个C类,被D类引用,那么当加载D类时就有3种情况,其中一种就是说,加载完D以后再加载所有D引用到的类,所以虚拟机又发起一个loadClassInternal.也就是说用加载D的classLoader去加载 包括去加载父类。

但一般来说父类已经被加载了,只不过上面写的loader并没有遵循委托策略,所以又重复define一次

也就是自定义的loader总有一个父loader,loadClass时总是先问父是否已加载,最后才是自己加载,也就是说,自己只负责自己关心得那些目录下的类文件,其他的都一级级委托给其他loader。一般来说是采用父子委托,但中间件有时也采用向下委托,反正要保证loader之间不会有交叉的边界,否则就乱了,所以自己实现loader,一般不改写loadClass方法,只复写findClass,因为这种策略其实已经内置了。
0 请登录后投票
   发表时间:2008-09-23  
hi ttitfly
你好 能不能把你能运行的代码再上传一下.
0 请登录后投票
   发表时间:2008-09-23  
把public Class loadClass( String name, boolean resolve ) throws ClassNotFoundException函数注释掉就可以了
0 请登录后投票
   发表时间:2008-10-17  

  最近搜索class文件加密的方法,去重写ClassLoader类.也遇到实例化两次的情况.

  借宝地一用

  我自定义的类加载器,在

       protected final Class<?> defineClass(String name,

                                     byte[] b,
                                     int off,
                                     int len)
                              throws ClassFormatError
定义类的时候,(目标类是  %classpath%/applicationmain.MainGui.class ).    

   总是抛出异常

     Exception in thread "main" java.lang.NoClassDefFoundError: MainGui (wrong name: applicationmain/MainGui)

	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
	at myclassloader.MyClassLoader.findClass(MyClassLoader.java:186)
	at myclassloader.MyClassLoader.loadClass(MyClassLoader.java:145)
	at myclassloader.MyClassLoader.loadClass(MyClassLoader.java:85)
	at myclassloader.MyClassLoader.main(MyClassLoader.java:435)

        

    但是如果我在classpath下直接定义一个Test类.即路径是 %classpath%/Test.class  类加载就是成功的.定义类跟包有关系吗?

    

 

   另外, defineClass(String name, byte[] b, int off, int len) 方法中的第一参数name是要加载类的全名,即 "包名.类名"  吗?而不是类名吗?   如defineClass("application.Test",.....

                                        或者defineClass("Test",.....

                                        

                                                

    

   


 

 

 

1 请登录后投票
论坛首页 入门技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics