`
banxi1988
  • 浏览: 151927 次
  • 性别: Icon_minigender_1
  • 来自: 桂林
社区版块
存档分类
最新评论

不同ClassLoader加载的类,能cast赋值啊!

阅读更多
我在学习ClassLoader的时候.
参照下面的地址的文章:深入探讨 Java 类加载器
其中讲到下面的这段:
引用

类加载器的代理模式
类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA 和 ClassLoaderB 分别读取了这个 Sample.class 文件,并定义出两个 java.lang.Class 类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException。下面通过示例来具体说明。代码清单 3 中给出了 Java 类 com.example.Sample。

其中清单3的代码如下:
package com.example; 
public class Sample { 
    private Sample instance; 

    public void setSample(Object instance) { 
        this.instance = (Sample) instance; 
    } 
}


一个测试方法如下: 即代码清单5.

public void testClassIdentity() { 
    String classDataRootPath = "C:\\workspace\\Classloader\\classData"; 
    FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); 
    FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); 
    String className = "com.example.Sample"; 	
    try { 
        Class<?> class1 = fscl1.loadClass(className); 
        Object obj1 = class1.newInstance(); 
        Class<?> class2 = fscl2.loadClass(className); 
        Object obj2 = class2.newInstance(); 
        Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class); 
        setSampleMethod.invoke(obj1, obj2); 
    } catch (Exception e) { 
        e.printStackTrace(); 
    } 
}


文章对此的测试结果是如下:
引用

java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26)
at classloader.ClassIdentity.main(ClassIdentity.java:9)
Caused by: java.lang.ClassCastException: com.example.Sample
    cannot be cast to com.example.Sample
at com.example.Sample.setSample(Sample.java:7)
... 6 more

结果解释,如上,和下面:
引用

从 代码清单 5 给出的运行结果可以看到,运行时抛出了 java.lang.ClassCastException 异常。虽然两个对象 obj1 和 obj2 的类的名字相同,但是这两个类是由不同的类加载器实例来加载的,因此不被 Java 虚拟机认为是相同的。
了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object 类,也就是说在运行的时候,java.lang.Object 这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object 类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。这种技术在许多框架中都被用到,后面会详细介绍。


但是,
但是我测试在机子上测试的时候,运行了N多次都没有见到,异常出来,我是第一次希望出现异常,
但是没有出现.
我测试代码如下,其它的没有变,就是包名变了,和一些路径变了.:
package guet.dream.jvm;

public class Sample {

	private Sample instance;
	
	public void setSample(Object instance){
		this.instance = (Sample)instance;
	}
}



测试方法:
package guet.dream.jvm;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassIndentityTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String classDataRootPath = "guet/dream/jvm/";
		FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
		FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
		String className = "guet.dream.jvm.Sample";
		
		try {
			Class<?> clazz1 = fscl1.loadClass(className);
			Class<?> clazz2 = fscl2.loadClass(className);
			
			Object obj1 = clazz1.newInstance();
			Object obj2 = clazz2.newInstance();
			
			Method setSampleMethod = clazz1.getMethod("setSample", Object.class);
			setSampleMethod.invoke(obj1, obj2);
		} catch (ClassNotFoundException e) {
			
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			
			e.printStackTrace();
		} catch (SecurityException e) {
		
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			
			e.printStackTrace();
		}

	}

}


下面是原文中,我稍加修改的ClassLoader.
package guet.dream.jvm;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class FileSystemClassLoader extends ClassLoader {

	private String rootDir;
	public FileSystemClassLoader(String rootDir){
		this.rootDir = rootDir;
	}
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException{
		byte[] classData = getClassData(name);
		if(classData == null){
			throw new ClassNotFoundException();
		}else{
			return defineClass(name, classData, 0, classData.length);
		}
	}

	private byte[] getClassData(String className) {
		String path = classNameToPath(className);

			InputStream in;
			try {
				in = new FileInputStream(path);
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				int bufferSize = 4096;
				byte[] buffer = new byte[bufferSize];
				int bytesNumRead = 0;
				while((bytesNumRead = in.read(buffer)) != -1	){
					baos.write(buffer,0,bytesNumRead);
				}
				return baos.toByteArray();
			} catch (FileNotFoundException e) {
				
				e.printStackTrace();
			} catch (IOException e) {
				
				e.printStackTrace();
			}


		
		return null;
	}

	private String classNameToPath(String className) {
		return rootDir+File.separatorChar+className.replace('.', File.separatorChar)+".class";
	}
}


运行结果没有任何异常,希望,有对这方面了解的,各位网友,测试或者给解答下.
这好像是我发的第一个贴吧!
我的JDK信息如下:
引用

banxi1988@banxi:~$ java -version
java version "1.6.0_22"
OpenJDK Runtime Environment (IcedTea6 1.10.2) (6b22-1.10.2-0ubuntu1~11.04.1)
OpenJDK Server VM (build 20.0-b11, mixed mode)
banxi1988@banxi:~$

分享到:
评论
2 楼 vavi 2013-08-06  
这个原因应该是你的类放在系统的classpath下面,由系统类加载器加载了,而不是你的自定义类加载器.
你仅仅覆写了findClass方法,但是findClass方法时被loadClass方法调用的,loadClass方法仍然是双亲委派的.

你可以尝试将需要加载的类放到非classpath目录下面,应该就会出现报错了.

1 楼 xyg123_1979163.com 2013-07-12  
因为你的FileSystemClassLoader没有指定其父ClassLoader,他默认就会使用当前线程的ContextClassloader作为其父Classloader,因为guet.dream.jvm.Sample类已经被当前线程的ContextClassloader所加载,当你用main中定义的两个FileSystemClassLoader去加载这个类时,都是首先委托给他们的父加载器进行加载,所以得到的Class对象实际上是由当前线程的Classloader加载的,因此不会抛出异常

相关推荐

Global site tag (gtag.js) - Google Analytics