ClassLoader

public class ClassLoaderTest
{
/**
Java的类加载器采用了一种父委托机制来加载需要的类.每个ClassLoader都关联一个父ClassLoader,
除了BootstrapClassLoader(启动类加载器)外.Java默认实现了三个类加载器:
BootstrapClassLoader(最顶层的类加载器),ExtClassLoader(扩展类加载器),AppClassLoader(系统类加载器),
其中ExtClassLoader的父加载器是BootstrapClassLoader,而AppClassLoader的父加载器是
ExtClassLoader.
BootstrapClassLoader默认从sun.boot.class.path环境变量设置的路径加载类文件,
ExtClassLoader默认从java.ext.dirs环境变量设置的路径加载类文件,AppClassLoader默认是从
java.class.path环境变量设置的路径加载类文件.
当一个类加载器试图加载一个类时,会先在自己的本地缓存(表达不是很恰当)中查找,如果之前已经加载过
这个类,就直接返回.如果没找到就委托给父加载器去加载,没找到继续往上查找,
也就是说一开始让最高层的父加载器加载这个类,父加载器加载成功,直接返回,并将这个类对象缓存起来,
如果加载不了,就让第二层父加载器加载,直到找到自己,如果自己也不能加载这个类,抛出ClassNotFoundException

因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态
替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,
因为String已经在启动时就被引导类加载器(BootstrapClassLoader)加载,
所以用户自定义的ClassLoader永远也无法加载一个自己写的String,
除非你改变JDK中ClassLoader搜索类的默认算法(重写loadClass方法)

JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。
只有两者同时满足的情况下,JVM才认为这两个class是相同的。
*/

// 启动类加载器的类加载路径
private static void getBootstrapClassPath2()
{
/**
* 结果: file:/C:/Program%20Files/Java/jdk1.6.0_17/jre/lib/resources.jar
* file:/C:/Program%20Files/Java/jdk1.6.0_17/jre/lib/rt.jar
* file:/C:/Program%20Files/Java/jdk1.6.0_17/jre/lib/sunrsasign.jar
* file:/C:/Program%20Files/Java/jdk1.6.0_17/jre/lib/jsse.jar
* file:/C:/Program%20Files/Java/jdk1.6.0_17/jre/lib/jce.jar
* file:/C:/Program%20Files/Java/jdk1.6.0_17/jre/lib/charsets.jar
* file:/C:/Program%20Files/Java/jdk1.6.0_17/jre/classes/
*/
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls)
{
System.out.println(url);
// 获取没有被转义的路径
// System.out.println(url.toURI().getPath());
}
}

// 启动类加载器的类加载路径
private static void getBootstrapClassPath()
{
// 直接从环境变量获取启动类加载器的类加载路径
/**
* 结果: C:\Program Files\Java\jdk1.6.0_17\jre\lib\resources.jar
* C:\Program Files\Java\jdk1.6.0_17\jre\lib\rt.jar C:\Program
* Files\Java\jdk1.6.0_17\jre\lib\sunrsasign.jar C:\Program
* Files\Java\jdk1.6.0_17\jre\lib\jsse.jar C:\Program
* Files\Java\jdk1.6.0_17\jre\lib\jce.jar C:\Program
* Files\Java\jdk1.6.0_17\jre\lib\charsets.jar C:\Program
* Files\Java\jdk1.6.0_17\jre\classes 自己添加的,JDK默认不包含这个目录
*/

// 在VM参数中加入选项:-Xbootclasspath/a:c:\cl.jar
// 表示在原有的启动类加载器的类加载路径后面加上一个c:\cl.jar包
// 选项-verbose 可以输出虚拟机启动过程中类被加载的详细信息
String bootstrapClassPath = System.getProperty("sun.boot.class.path");
String[] parts = bootstrapClassPath.split(";");
for (String part : parts)
System.out.println(part);
}

private static void classLoaderHierarchy()
{
/**
* 结果: Current class loader: sun.misc.Launcher$AppClassLoader@47858e
* Parent class loader: sun.misc.Launcher$ExtClassLoader@19134f4 Parent
* class loader: null
*/
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
System.out.println("Current class loader: " + loader);
while (loader != null)
{
loader = loader.getParent();
System.out.println("Parent class loader: " + loader);
}
}
}
public class URLClassLoader extends ClassLoader
{/**
* 自定义类加载器
*/
public static void main(String[] args) throws Exception
{
String baseURL = "http://localhost:8080/rat/classes";
URLClassLoader ucl = new URLClassLoader(baseURL, null);
Class<?> clazz = ucl.loadClass("com.classloader.SimpleObj");
System.out.println("class: " + clazz);
//父加载器找不到SimpleObj,所以最终由我们自己定义的类加载器加载
//class loader: com.classloader.URLClassLoader@1034bb5
System.out.println("class loader: " + clazz.getClassLoader());

//SimpleObj默认由AppClassLoader加载,此时SimpleObj存在classpath中
//所以定义类加载器(最终执行类加载的那个类加载器)为AppClassLoader
SimpleObj obj = new SimpleObj();
System.out.println("class: " + obj.getClass());
//class loader: sun.misc.Launcher$AppClassLoader@47858e
System.out.println("class loader: " + obj.getClass().getClassLoader());

//target的类文件是由我们自定义类加载器加载的
//obj的类文件是由系统类加载器加载的 是两个不同的类型
Object target = clazz.newInstance();
System.out.println("target: " + target);
Method m = clazz.getMethod("setObj", Object.class);
System.out.println(m);
//所以此时利用反射调用target对象的setObj会出现类型转异常
//Caused by: java.lang.ClassCastException: com.classloader.SimpleObj
//cannot be cast to com.classloader.SimpleObj
//at com.classloader.SimpleObj.setObj(SimpleObj.java:34)
m.invoke(target, obj);
}

private String baseURL;

public URLClassLoader(String baseURL)
{
this(baseURL, getSystemClassLoader());
}

public URLClassLoader(String baseURL, ClassLoader parent)
{
super(parent);
this.baseURL = baseURL;
}

//loadClass中定义了类加载的规则,如果遍历完所有的父加载器后,都不能完成类的加载,
//就会调用findClass方法去加载所要的类,所以实现自己的类加载器时,只要覆写findClass即可
@Override
public Class<?> findClass(String name) throws ClassNotFoundException
{
byte[] data = getClassData(name);
if (data == null)
throw new ClassNotFoundException(name);
return defineClass(name, data, 0, data.length);
}

private byte[] getClassData(String name)
{
InputStream in = null;
try
{
URL url = new URL(className2FilePath(name));
in = url.openStream();
byte[] buf = new byte[4 * 1024];
int len = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();

while (-1 != (len = in.read(buf)))
baos.write(buf, 0, len);

return baos.toByteArray();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
if (in != null)
{
try
{
in.close();
}
catch (IOException e)
{
}
}
}
return null;
}

private String className2FilePath(String name)
{
return baseURL + "/" + name.replace(".", "/") + ".class";
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值