類載入器--Tomcat--ParallelWebappClassLoader
首先是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;
}