计算机二级辅导:java类装载(2)
发布时间:2010/3/13 9:06:57 来源:城市学习网 编辑:MOON
在这种情况下,系统会提示我们出现异常,因为我们有两个相同的类,一个是真正的URL,一个是我在上面实现的伪类;出现异常是正常的,因为你想想,如果我们在执行一个applet的时候,程序自己实现了一个String的类覆盖了我们虚拟机上面的真正的String类,那么在这个String里面,不怀好意的人可以任意的实现一些功能;这就造成极不安全的隐患;所以java采用了一种名为“双亲委托”的加载模式;
以下是jdk源代码:
protected synchronized Class? loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
在上面的代码中,我们可以清晰的看见,我们调用一个ClassLoader加载程序的时候,这个ClassLoader会先调用设置好的 parent ClassLoader来加载这个类,如果parent是null的话,则默认为Boot ClassLoader类,只有在parent没有找的情况下,自己才会加载,这就避免我们重写一些系统类,来破坏系统的安全;
再来看一个明显的例子:
package org.corey;
public class MyCls{
private String name;
public MyCls(){
}
public MyCls(String name){
this.name=name;
}
public void say(){
System.out.println(this.name);
}
}
把上面这个MyCls类打成jar包,丢进ext classLoader的加载路径;
然后写出main类:
package org.corey.clsloader;
import org.corey.MyCls;
public class TheSameClsDemo {
/**
* @param args
*/
public static void main(String args) {
MyCls myClsOb=new MyCls("name");
myClsOb.say();
System.out.println(MyCls.class.getClassLoader());
System.out.println(System.getProperty("java.class.path"));
System.out.println(TheSameClsDemo.class.getClassLoader());
}
}
并且把MyCls类加入biild-path里面方便引用;
结果是:
name
sun.misc.Launcher$ExtClassLoader@16930e2
E:“workspace“ClassLoaderDemo“bin;E:“MyEclipse 6.0“jre“lib“ext“corey.jar
sun.misc.Launcher$AppClassLoader@7259da
从上面的例子可以清晰的看出ClassLoader之间的这种双亲委托加载模式;
再来看下一个例子(http://bbs.cnw.com.cn/viewthread.php?tid=95389)
下面我们就来看一个综合的例子。首先在eclipse中建立一个简单的java应用工程,然后写一个简单的JavaBean
package classloader.test.bean;
publicclass TestBean {
public TestBean() {}
}
在现有当前工程中另外建立一测试类(ClassLoaderTest.java)内容
测试一:
publicclass ClassLoaderTest {
publicstaticvoid main(String args) {
try {
//查看当前系统类路径中包含的路径条目
System.out.println(System.getProperty("java.class.path"));
//调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean
Class typeLoaded = Class.forName("classloader.test.bean.TestBean");
//查看被加载的TestBean类型是被那个类加载器加载的
System.out.println(typeLoaded.getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
对应的输出
D:"DEMO"dev"Study"ClassLoaderTest"bin
sun.misc.Launcher$AppClassLoader@197d257
(说明:当前类路径默认的含有的一个条目就是工程的输出目录)
测试二:
将当前工程输出目录下的…/classloader/test/bean/TestBean.class打包进test.jar剪贴到 Java_Runtime_Home /lib/ext目录下(现在工程输出目录下和JRE扩展目录下都有待加载类型的class文件)。再运行测试一测试代码,结果
D:"DEMO"dev"Study"ClassLoaderTest"bin
sun.misc.Launcher$ExtClassLoader@7259da
对比测试一和测试二,我们明显可以验证前面说的双亲委派机制,系统类加载器在接到加载classloader.test.bean.TestBean类型的请求时,首先将请求委派给父类加载器(标准扩展类加载器),标准扩展类加载器抢先完成了加载请求。
测试三:
将test.jar拷贝一份到 Java_Runtime_Home /lib下,运行测试代码,输出
D:"DEMO"dev"Study"ClassLoaderTest"bin
sun.misc.Launcher$ExtClassLoader@7259da
测试三和测试二输出结果一致。那就是说,放置到 Java_Runtime_Home /lib目录下的TestBean对应的class字节码并没有被加载,这其实和前面讲的双亲委派机制并不矛盾。虚拟机出于安全等因素考虑,不会加载 Java_Runtime_Home /lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。做个进一步验证,删除 Java_Runtime_Home /lib/ext目录下和工程输出目录下的TestBean对应的class文件,然后再运行测试代码,则将会有 ClassNotFoundException异常抛出。有关这个问题,大家可以在java.lang.ClassLoader中的 loadClass(String name, boolean resolve)方法中设置相应断点运行测试三进行调试,会发现findBootstrapClass0()会抛出异常,然后在下面的findClass 方法中被加载,当前运行的类加载器正是扩展类加载器(sun.misc.Launcher$ExtClassLoader),这一点可以通过JDT中变量视图查看验证。
(5)被不同的ClassLoader加载的两个类之间有什么限制和不同?
现在我们来看一下一个现象:
在eclipse里面我是这样做的:
OneCls.java
package org.corey.one;
import org.corey.two.TwoCls;
public class OneCls {
public OneCls() {
System.out.println();
TwoCls two = new TwoCls();
two.say();
}
}
TwoCls.java
package org.corey.two;
public class TwoCls {
public void say() {
System.out.println("i am two");
}
}
Demo.java:
package org.corey.Demo;
import org.corey.one.OneCls;
public class Demo {
/**
* @param args
*/
public static void main(String args) {
OneCls one=new OneCls();
}
}
在这里,我们来仔细看下,one引用了two,demo引用了one,这是三个类都是由AppClassLoader加载的;运行正常;
把OneCls打成jar包,放在lib/ext路径下面,然后在工程里面引入这个jar包;运行:异常,这是因为:
Demo是由AppClassLoader载入,委托给双亲加载失败后,由AppClassLoader加载,而加载OneCls的时候,委托给双亲,被ExtClassLoader加载成功,但是在载入OneCls的时候,同时引用了TwoCls,但是ExtClassLoader引用 TwoCls失败,但是他只会委托给双亲,而不会委托给AppClassLoader这个儿子,所以会出现异常;
: