ClassLoader

来源:互联网 发布:意大利设计风格知乎 编辑:程序博客网 时间:2024/06/09 14:29
ClassLoader本身是一个Abstract Class,我们可以扩展ClassLoader的实现特定的load需求。
java应用环境中不同的class分别由不同的ClassLoader负责加载。
一个jvm中默认的classloader有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader,分别各司其职:

Bootstrap ClassLoader     负责加载java基础类,主要是 %JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等

Extension ClassLoader     负责加载java扩展类,主要是 %JRE_HOME/lib/ext 目录下的jar和class
App ClassLoader           负责加载当前java应用的classpath中的所有类。 


加载机制:

出于代码安全性考虑,ClassLoader采用的是双亲委托的加载模式,否则用户会出现用户自定义的ClassLoader加载的类会覆盖jvm加载的类。图解:


classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入。
所以,当我们自定义的classloader加载成功了com.company.MyClass以后,MyClass里所有依赖的class都由这个classLoader来加载完成。


正常的双亲委派模型中,下层的类加载器可以使用上层父加载器加载的对象,但是上层父类的加载器不可以使用子类加载的对象。
而有些时候程序的确需要上层调用下层,这时候就需要ContextClassLoader 线程上下文加载器来处理。使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类。

以JNDI为例,它的核心是由JRE核心类(rt.jar)实现的。但这些核心JNDI类必须能加载由第三方厂商提供的JNDI实现。
解决办法就是让核心JNDI类使用线程上下文类加载器,从而有效的打通类加载器层次结构,逆着代理机制的方向使用类加载器。

线程上下文从根本解决了一般应用不能违背双亲委派模式的问题,使java类加载体系显得更灵活.使用线程上下文加载类。线程将会从它的父线程中继承上下文类加载器。如果你在整个应用中不做任何设置,所有线程将以系统类加载器作为它们自己的上下文加载器。
也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,防止因为不同的类加载器, 导致类型转换异常(ClassCastException)。
如果多个JVM线程通过共享数据通信时这将造成一个非常混乱的类加载图景,除非他们都使用同一个上下文加载器实例。

同时ClassLoader也充当了namespace的角色,不同的ClassLoader加载的同一个class也是不同的,相互之间做类型转换是要抛 castException的。
注意ClassLoader对findClass的定义包含synchronized关键字,保证在多线程环境下,多个线程不会在读入同一个Class,以免造成互锁的情况。

ClassLoader加载定义实例

ClassLoader定义的用于加载类的方法,一般我们只需要实现findClass:
findClass(String name)   loadClass(String name)   protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError  

每一个JVM环境中,都有一个默认的ClassLoader,我们可以通过
ClassLoader.getSystemClassLoader()
方法获得默认的ClassLoader(AppClassLoader): 

我们可以通过loadClass方法来加载名为className的类: 

Class clazz = ClassLoader.loadClass("MyClass");  

然后调用Class的newInstance创建这个类的实例:
MyClass myClass = (MyClass) clazz.newInstance(); 

不同的 classLoader 之间怎么相互通信,调用彼此的服务实现呢?如果可以尽量是在同一个classloader下加载要用到的类,使用反射的方式来调用类的实例是一种方式。

虽然我们用不同的ClassLoader加载的同一个Class,不能相互Cast,但是我们可以坚持全部代码使用反射方式进行调用。


附例:

工程A里面定义Product接口,和接口实现ProductImpl;

工程B里面定义SimpleClassLoader,和主函数测试入口类。

package com.study.classloader;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class SimpleClassLoader extends ClassLoader {String[] dirs;public SimpleClassLoader(String path) {dirs = path.split(System.getProperty("path.separator"));String[] _dirs = dirs.clone();for (String dir : _dirs) {extendClasspath(dir);}}public void extendClasspath(String path) {String[] segments = path.split("/");String[] exDirs = new String[segments.length];for (int i = 0; i < (segments.length); i++) {exDirs[i] = popd(segments, i);}String[] newDirs = new String[dirs.length + exDirs.length];System.arraycopy(dirs, 0, newDirs, 0, dirs.length);System.arraycopy(exDirs, 0, newDirs, dirs.length, exDirs.length);dirs = newDirs;}private String popd(String[] pathSegments, int level) {StringBuffer path = new StringBuffer();for (int i = 0; i < level; i++) {path.append(pathSegments[i]).append("/");}return path.toString();}public String[] getDirs() {return dirs;}public synchronized Class<?> findClass(String name) throws ClassNotFoundException {for (String dir : dirs) {byte[] buf = getClassData(dir, name);if (buf != null) {System.out.println("Loaded '" + name + "' from: " + dir);return defineClass(name, buf, 0, buf.length);}}throw new ClassNotFoundException();}protected byte[] getClassData(String directory, String name) {String[] tokens = name.split("\\.");String classFile = directory + "/" + tokens[tokens.length - 1]+ ".class";File f = (new File(classFile));int classSize = (new Long(f.length())).intValue();byte[] buf = new byte[classSize];try {FileInputStream filein = new FileInputStream(classFile);classSize = filein.read(buf);filein.close();} catch (FileNotFoundException e) {return null;} catch (IOException e) {return null;}catch (Exception e) {return null;}return buf;}}


package com.study.classloader;import java.lang.reflect.Method;public class ClassLoaderDemo {public static void main(String args[]){          ClassLoader cl = new SimpleClassLoader("D:/work/workspace_bak/Test/bin/com/study/classloader/impl");        ClassLoader cl_1 = new SimpleClassLoader("D:/work/workspace_bak/Test/bin/com/study/classloader/impl");                  try {Class clazz = cl.loadClass("com.study.classloader.impl.ProductImpl");Class clazz_1 = cl_1.loadClass("com.study.classloader.impl.ProductImpl"); // 查看各自己使用的ClassLoader  System.out.println(clazz.getClassLoader());  System.out.println(clazz_1.getClassLoader());    // 看看JVM是否认为clazz和clazz_1是同一个Class  System.out.println("clazz & clazz_1 is the same Class ? "+(clazz == clazz_1));  //用反射的方式去执行方法Object obj = clazz.newInstance();Method[] mthds = clazz.getDeclaredMethods();for (Method mthd : mthds) {String methodName = mthd.getName();//excute methodString arg = "iphone5S";mthd.invoke(obj, arg);System.out.println("mthd.name=" + methodName);}System.out.println("obj.class=" + obj.getClass().getName());System.out.println("obj.class=" + clazz.getClassLoader().toString());System.out.println("obj.class="+clazz.getClassLoader().getParent().toString());} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}      }  }


实例加载实现jar里面的main

package com.study.classloader;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Modifier;import java.net.JarURLConnection;import java.net.URL;import java.net.URLClassLoader;import java.util.jar.Attributes;public class JarClassLoaderDemoRun {/** * 根据URL找到该Jar的Main方法类名. *  * @param url *            Jar的Url:Demo.jar & HelloDemo.jar * @return String * @throws IOException */public static String getMainClassName(URL url) throws IOException {JarURLConnection uc = (JarURLConnection) url.openConnection();Attributes attr = uc.getMainAttributes();return attr != null ? attr.getValue(Attributes.Name.MAIN_CLASS) : null;}/** * 通过反射执行类Main方法. *  * @param classLoader *            自定义的ClassLoader * @param name *            Jar的Main方法名 * @param args *            Main方法参数 * @throws ClassNotFoundException * @throws NoSuchMethodException * @throws InvocationTargetException */public static void invokeClass(ClassLoader classLoader, String name,String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException {Class c = classLoader.loadClass(name);Method m = c.getMethod("main", new Class[] { args.getClass() });m.setAccessible(true);int mods = m.getModifiers();if (m.getReturnType() != void.class || !Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {throw new NoSuchMethodException("main");}try {m.invoke(null, new Object[] { new String[] {} });} catch (IllegalAccessException e) {e.printStackTrace();} catch (IllegalArgumentException e) {e.printStackTrace();}}public static void main(String[] args) {try {System.out.println("ClassLoaderRun ClassLoader:" + JarClassLoaderDemoRun.class.getClassLoader());ClassLoader cl = Thread.currentThread().getContextClassLoader();URL[] urls = new URL[] { new URL("file:/C:/Demo.jar"),  new URL("file:/D:/study/code/HelloDemo.jar") };// 自定义ClassLoader,通过指定URL加载JarURLClassLoader classLoader = new URLClassLoader(urls, cl);Thread.currentThread().setContextClassLoader(classLoader);// 程序入口取得Jar中Main方法类名String mainClass = getMainClassName(new URL("jar:file:/D:/study/code/HelloDemo.jar!/"));invokeClass(classLoader, mainClass, new String[] {});} catch (Throwable e) {e.printStackTrace();}}}


refer: http://weli.iteye.com/blog/1682625



原创粉丝点击