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"; } }