标准JVM的3个classloader以及原理就不多说了. Tomcat自己的classloader是WebAppClassLoader. 每个WEB应用运行与自己的WebAppClassLoader中.在之前,这个classloader和system classloader之间还有一个tomcat的StandardClassLoader. 但在新的版本中,standardclassloader已经废除了. WebAppClassLoader的parent就直接是JVM的system classloader.
照例,为了防止web里的class覆盖了system里面的标准class, web classloader依然是parent优先加载的方式. WebAppClassLoader主要是从2个地方搜索class文件. WEB-INF/classes, WEB-INF/lib. 其中classes的优先级要高,这意味着,如果lib中的某个jar包含着和classes下面同样的class文件, classes下面的class将被加载.
另外,两个并行的webappclassloader加载了同样的class,它们可以在各自的classloader命名空间下正常工作,而相互不打扰.
最后,parent classloader是无法load到child classloader下的class的, 这怎么办? 于是有了Thread.currentThread().getContextClassLoader(). 使用它去load自己看不见的class. 在这之前,每个thread要适当正确的设置setContextClassLoader().
以上理论都是常识了.我只是想写点代码,模拟webappclassloader. 使用自己的classloader来测试一些问题各种class 冲突问题.
我的classloader
1, 它有一个工作目录.
2, 它搜索工作目录下的两个子目录classes和classes2,从中加载class. 其中classes优先级高于classes2.
实现方法:
1, 创建两个java project. 一个用来实现classloader. 另一个用来写classloader要加载的classes.
2, 两个项目使用interface Test作为结合.classloader会加载一个Test的实现类,然后将其实例化并调用其test()方法.
3, Main方法了面将启动两个classloader, 分别加载自己的Test类. 它们可以互不干扰的各自工作.每个Test都会调用自己的Printer来打印自己的static field. 可以看到一个现象,虽然两个classloader加载了同样的类,两个类的static field都各自拥有,互不干扰.它们是不共享的.
运行方法:
1, 首先为即将运行的两个classloader分别创建工作目录, work1和work2. 在每个工作目录下再创建classes和classes2目录.
2, 将接口Test.java和两个实现类Printer.java,TestImpl.java下载下来,放入一个java project.编译好以后,将其class文件复制到每个工作目录的classes或者classes2下面.
3, 将接口Test.java,MyClassLoader.java和main函数所在类Go.java下载下来,放入eclipse project中.
4, 修改Go文件里面的工作目录路径.传递正确的值给两个classLoader.
5, 运行Go.
6, 修改Go,运行Go,以达到各种测试目的.
接口Test.java
package classLoader;
public interface Test {
public void test() throws InterruptedException;
}
MyClassLoader.java
package classLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
public class MyClassLoader extends URLClassLoader {
// System class loader, or so called application class loader.
private ClassLoader system;
// The working directory of this class loader. Recources and classes
// are loaded from here.
private String workDir;
// Loaded class cache
private class Entry{
String name;
Class loadedClass;
long modifiedDate;
String path;
}
private Map<String, Entry> cache;
public MyClassLoader(String workDir) {
super(new URL[0]);
this.workDir = workDir;
this.system = this.getSystemClassLoader();
this.cache = new HashMap<String, Entry>();
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> clazz = null;
// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
return (clazz);
}
// (0.1) Check our previously loaded class cache
clazz = findLoadedClass(name);
if (clazz != null) {
return (clazz);
}
// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding J2SE classes
try {
clazz = system.loadClass(name);
if (clazz != null) {
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
// (1) Search local repositories
clazz = findClass(name);
return clazz;
}
// search all classes under <workDir>/classes and <workDir>/classes2.
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clazz;
// search classes first.
String path = this.workDir+"classes/"+name.replace('.', '/')+".class";
File file = new File(path);
if (file.exists()) {
ByteBuffer buffer = ByteBuffer.allocate((int)file.length()+1000);
try {
new FileInputStream(file).getChannel().read(buffer);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
buffer.flip();
clazz = this.defineClass(name, buffer.array(), 0, (int)file.length());
Entry entry = new Entry();
entry.loadedClass = clazz;
entry.name = name;
entry.modifiedDate = file.lastModified();
entry.path = file.getAbsolutePath();
// put it into cache.
this.cache.put(name, entry);
return clazz;
}
// search classes2 secondly.
path = this.workDir+"classes2/"+name.replace('.', '/')+".class";
file = new File(path);
if (file.exists()) {
ByteBuffer buffer = ByteBuffer.allocate((int)file.length()+1000);
try {
new FileInputStream(file).getChannel().read(buffer);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
buffer.flip();
clazz = this.defineClass(name, buffer.array(), 0, (int)file.length());
Entry entry = new Entry();
entry.loadedClass = clazz;
entry.name = name;
entry.modifiedDate = file.lastModified();
entry.path = file.getAbsolutePath();
this.cache.put(name, entry);
return clazz;
}
throw new ClassNotFoundException();
}
protected Class<?> findLoadedClass0(String name) {
Entry entry = cache.get(name);
if (entry != null && !this.isModified(entry)) {
return entry.loadedClass;
}
return (null);
}
private boolean isModified(Entry entry) {
if (new File(entry.path).lastModified() != entry.modifiedDate) return true;
return false;
}
}
Go.java
package classLoader;
public class Go {
public static void main(String[] args) throws Exception {
ClassLoader cl = new MyClassLoader("work1/");
Class<?> clazz = cl.loadClass("myClasses.TestImpl");
Test test = (Test)clazz.newInstance();
test.test();
ClassLoader cl2 = new MyClassLoader("work2/");
Class<?> clazz2 = cl2.loadClass("myClasses.TestImpl");
Test test2 = (Test)clazz2.newInstance();
test2.test();
test.test();
test2.test();
Thread.currentThread().setContextClassLoader(cl2);
}
}
以上是模拟webappclassloader. 下面将模拟web实现
TestImpl.java
package myClasses;
import java.util.Random;
import classLoader.Test;
public class TestImpl implements Test {
public static int number = -1;
private Printer printer;
public TestImpl() {
printer = new Printer();
}
public void test() throws InterruptedException {
if (number<0) number = Math.abs(new Random().nextInt(30));
Thread.yield();
Thread.sleep(1000);
Thread.yield();
printer.print();
}
}
Printer.java
package myClasses;
public class Printer {
public void print() {
System.out.println("Classloader is: " + this.getClass().getClassLoader());
System.out.println("The number is: " + TestImpl.number);
}
}