一、序言
先啰嗦一下,上次,阿里面试的时候问到能否加载一个java.lang.xx 的类,我的回答的是不能- -!当然答案是正确的,但是不知道为什么。
还有一个问题:如果加载两个jar,里面含有相同路径的类,是可以的吗?我还是回答不能- -,估计会冲突,但是原因也是模模糊糊,这里我再回顾一自定义加载类的方法吧,至于原理和细节的介绍,在JVM 目录下有。
二、自定义类加载器:
这里还是先写一个简单的加载器,方便测试吧!
/**
* 自定义加载器的工具类
* 临时写的,暂时不做过多控制
* @author Ran
*
*/
public class ClassLoaderUtils extends ClassLoader{
// 获取二进制字节流
private byte[] getData(InputStream in){
byte[] data = null;
try {
data = new byte[in.available()];
in.read(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(in != null){
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
// 获得文件流
private InputStream getInputStream(String name){
name = name.replace(".", "/")+".class";
return getClass().getClassLoader().getResourceAsStream(name);
}
// 这个源码是直接抛出异常,并且是protected,好让我们重写findClass
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
InputStream in = getInputStream(name);
if(in == null){
System.out.println("自定义加载失败!");
return super.loadClass(name);
}
byte[] data = this.getData(in);
return super.defineClass(name,data, 0, data.length);
}
// 测试
public static void main(String[] args) throws Exception,
IllegalAccessException, ClassNotFoundException {
//
String name = "com.T1";
ClassLoaderUtils cu = new ClassLoaderUtils();
System.out.println(cu.findClass(name).newInstance());
}
}
2.1 尝试加载java.lang.下面的类,这里我创建了一个 java.lang.LangTest 类,JDK1.6 进行测试。
// 测试
public static void main(String[] args) throws Exception, IllegalAccessException, ClassNotFoundException {
String name = "java.lang.LangTest";
ClassLoaderUtils cu = new ClassLoaderUtils();
System.out.println(cu.findClass(name).newInstance());
}
结果:Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
后面我继续测试,我同样用rt.jar 下面的目录结构,结果是:
java.io.xxx :异常同上
sun.nio.xxx: 没有异常。
也就说我们猜想,自定义加载器在加载rt.jar 里面,包含java 开头的会出错,从错误分析,有包名检测机制决定的。java 包下面的应该属于很核心的东西,不允许我们建立这些。
2.3 同一包下面的类,可以被多次加载吗?
String name = "com.Test3";
ClassLoaderUtils cu = new ClassLoaderUtils();
Object a = cu.findClass(name);
System.out.println(a);
Object b = cu.findClass(name);
System.out.println(b);
结果:
loader (instance of ClassLoaderUtils): attempted duplicate class definition for name: "com/Test3"
可以看出:不能重复加载,我们换种方式呢?
String name = "com.Test3";
ClassLoaderUtils cu = new ClassLoaderUtils();
Object a = cu.findClass(name);
System.out.println(a);
// 从新创建一个加载器
ClassLoaderUtils cu1 = new ClassLoaderUtils();
Object b = cu1.findClass(name);
System.out.println(b);
这个结果是正常的。
我们可以猜想:同一个加载器无法加载相同包的的同名类,在类加载机制里面,我们说到每个类在内存中只有一份,这里的一份是指拥有相同的类加载器的情况。因此很多第三方框架都喜欢用自己的类加载器,不至于重复。
小结:
1.关于类加载的东西,我是有些模糊的,比如第一个问题,我仅仅知道java.lang 下的不能自己加载,知道那是核心的东西,但是具体的原因,以及是实现过程,是没有研究的,比较清楚的大哥,希望可以介绍介绍。
2.关于类在内存中只有一份,这个概念,应该是规范要求的,毕竟我们存在积分相同的类是没意义的,会破坏我们的继承机制,比如有两份java.lang.Object 类,我们的子类到底属于哪个? 这里我尝试过,即使编译也通不过!
3.关于一个类多次加载的问题,我用 jar cvf 打包名.jar 目标.class 的命令打成两个不同的jar,然后放入工程,是没有错误的,但是只能调用其中一个类的方法, 比如a.jar 里面打印1,b.jar 打印2,那么我调方法的时候是打印的1,没有明确先后,都是App 的加载器。
4.上面仅仅是验证下效果,具体的原理没有详细分析,动动手,自己放心,详细的后面慢慢研究。