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和classApp 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 method
String 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 block
e.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加载Jar
URLClassLoader 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