首先是jvm自带的三个类加载器的关系图:
系统类加载器在加载一个类时,会先查找已经加载的类,如果没找到,再委托父加载器(父加载器不是父类,这是2个概念),父加载器没找到就继续委托父加载器,直到所有的父加载器都没有找到,并且都加载失败之后,就自己加载,如果自己加载也失败了,就抛异常。
父类加载过,而且还尝试加载失败,那么就自己来
c = findClass(name);
这个方法在urlClassLoader中实现。自定义类加载器,一般覆盖这个方法。调用protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)方法即可。
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
直接继承ClassLoader的类加载器,需要重写findClass方法,而且这样产生的类加载器依然是双亲委托的。
如果想破坏双亲委托机制,则还需要额外重写loadClass方法。
tomcat 拥有不同的自定义类加载器,以实现对各种资源库的控制。一般来说,tomcat主要用类加载器解决以下4个问题:
- 同一个web服务器里,各个web项目之间各自使用的Java类库要相互隔离
- 同一个web服务器中,各个web项目之间可以提供共享的Java类库
- 为了使服务器不受web项目的影响,应该使服务器的类库与应用程序的类库相互独立
- 对于jsp的web服务器,应该支持热插拔功能
tomcat的类加载器定义:
common类加载器负责加载%catalina_home%/lib,%catalina_base%/lib两个目录下的class文件和jar文件。
tomcat 7中默认左边两个类加载器都是common。
tomcat 7中对这些类加载器的初始化,根据catalina.properties中的server.loader和share.loader属性来判断要不要创建新的类加载器。
tomcat中有多少个web应用,就有多少个webappclassLader。
它使用ClassLoaderFactory 构建类加载器,common类加载器new URLClassLoader(array, parent);实际上就是urlclassloader。
父加载器是应用类加载器,tomcat会在启动时把当前线程类加载器设置为common类加载器。
public static ClassLoader createClassLoader(File[] unpacked, File[] packed, final ClassLoader parent) throws Exception
public static ClassLoader createClassLoader(List<ClassLoaderFactory.Repository> repositories, final ClassLoader parent) throws Exception
public static enum RepositoryType {
DIR, //加载目录下所有资源
GLOB, //整个目录下所有的jar包资源
JAR, //单个jar包资源
URL; //从url上获取的jar包资源
private RepositoryType() {
}
}
ParallelWebappClassLoader这类的主要逻辑是在父类中实现的,子类只有一个方法。用于热部署时重新加载所有的类,重新加载其实只需要新建一个类加载器把类再加载一遍就可以了,这里还保留了加载器之前的状态。
public ParallelWebappClassLoader copyWithoutTransformers() {
ParallelWebappClassLoader result = new ParallelWebappClassLoader(this.getParent());
super.copyStateWithoutTransformers(result);
try {
result.start();
return result;
} catch (LifecycleException var3) {
throw new IllegalStateException(var3);
}
}
protected void copyStateWithoutTransformers(WebappClassLoaderBase base) {
base.resources = this.resources;
base.delegate = this.delegate; //默认一般是false
base.state = LifecycleState.NEW;
base.clearReferencesStopThreads = this.clearReferencesStopThreads;
base.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
base.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
base.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
base.jarModificationTimes.putAll(this.jarModificationTimes);
base.permissionList.addAll(this.permissionList);
base.loaderPC.putAll(this.loaderPC);
}
delegate 为true时,这里的父类加载器 不是Java语法中的父类,只是单纯的父加载器。parent属性指定的。
为false时
再看核心方法start(),很明显,在重新创建一个新的类加载器后,会调用这个方法:
public void start() throws LifecycleException {
this.state = LifecycleState.STARTING_PREP;
WebResource classes = this.resources.getResource("/WEB-INF/classes");
if(classes.isDirectory() && classes.canRead()) {
this.localRepositories.add(classes.getURL());
}
WebResource[] jars = this.resources.listResources("/WEB-INF/lib");
WebResource[] var3 = jars;
int var4 = jars.length;
for(int var5 = 0; var5 < var4; ++var5) {
WebResource jar = var3[var5];
if(jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
this.localRepositories.add(jar.getURL());
this.jarModificationTimes.put(jar.getName(), Long.valueOf(jar.getLastModified()));
}
}
this.state = LifecycleState.STARTED;
}
父类WebappClassLoaderBase的核心代码是下面的部分,读取字节码,调用转换器,加载类。这些步骤和普通加载器区别不大。
byte[] binaryContent = resource.getContent();
Manifest manifest = resource.getManifest();
URL codeBase = resource.getCodeBase();
Certificate[] certificates = resource.getCertificates();
String packageName;
if(this.transformers.size() > 0) {
packageName = path.substring(1, path.length() - ".class".length());
Iterator var12 = this.transformers.iterator();
while(var12.hasNext()) {
ClassFileTransformer transformer = (ClassFileTransformer)var12.next();
try {
byte[] transformed = transformer.transform(this, packageName, (Class)null, (ProtectionDomain)null, binaryContent);
if(transformed != null) {
binaryContent = transformed;
}
} catch (IllegalClassFormatException var18) {
log.error(sm.getString("webappClassLoader.transformError", new Object[]{name}), var18);
return null;
}
}
}
packageName = null;
int pos = name.lastIndexOf(46);
if(pos != -1) {
packageName = name.substring(0, pos);
}
Package pkg = null;
if(packageName != null) {
pkg = this.getPackage(packageName);
if(pkg == null) {
try {
if(manifest == null) {
this.definePackage(packageName, (String)null, (String)null, (String)null, (String)null, (String)null, (String)null, (URL)null);
} else {
this.definePackage(packageName, manifest, codeBase);
}
} catch (IllegalArgumentException var17) {
;
}
pkg = this.getPackage(packageName);
}
}
if(this.securityManager != null && pkg != null) {
boolean sealCheck = true;
if(pkg.isSealed()) {
sealCheck = pkg.isSealed(codeBase);
} else {
sealCheck = manifest == null || !this.isPackageSealed(packageName, manifest);
}
if(!sealCheck) {
throw new SecurityException("Sealing violation loading " + name + " : Package " + packageName + " is sealed.");
}
}
try {
clazz = this.defineClass(name, binaryContent, 0, binaryContent.length, new CodeSource(codeBase, certificates));
} catch (UnsupportedClassVersionError var16) {
throw new UnsupportedClassVersionError(var16.getLocalizedMessage() + " " + sm.getString("webappClassLoader.wrongVersion", new Object[]{name}));
}
entry.loadedClass = clazz;
return clazz;
}