開篇
?這是一篇嘗試講解清楚Tomcat的類加載器的文章,估摸著能講清楚六成左右,待后續再理理思路。
?文末有彩蛋,可以直接翻到文章末尾。
Tomcat 類加載器概覽
Tomcat類加載器
說明:
- BootstrapClassLoader : 系統類加載器,加載%JAVA_HOME%/lib目錄下的jar
- ExtClassLoader : 擴展類加載器,加載%JAVA_HOME%/ext/lib目錄下的jar
- AppClassLoader : 普通類加載器,加載CLASSPATH指定目錄下的jar
- commonLoader : Tomcat 通用類加載器, 加載的資源可被 Tomcat 和 所有的 Web 應用程序共同獲取
- catalinaLoader : Tomcat 類加載器, 加載的資源只能被 Tomcat 獲取(但 所有 WebappClassLoader 不能獲取到 catalinaLoader 加載的類)
- sharedLoader : Tomcat 各個Context的父加載器, 這個類是所有 WebappClassLoader 的父類, sharedLoader 所加載的類將被所有的 WebappClassLoader 共享獲取
- 這個版本 (Tomcat 8.x.x) 中, 默認情況下 commonLoader = catalinaLoader = sharedLoader
- (PS: 為什么這樣設計, 主要這樣這樣設計 ClassLoader 的層級后, WebAppClassLoader 就能直接訪問 tomcat 的公共資源, 若需要tomcat 有些資源不讓 WebappClassLoader 加載, 則直接在 ${catalina.base}/conf/catalina.properties 中的 server.loader 配置一下 加載路徑就可以了)
Tomcat類關系圖
說明:
- Common、Catalina、Shared類加載器繼承自URLCLassLoader。
- WebappClassLoader繼承WebappClassLoaderBase繼承URLCLassLoader。
3、JVM自帶的ExtClassLoader也是URLClassLoader的子類。
4、JVM自帶的AppClassLoader也是URLClassLoader的子類。
Tomcat 各類ClassLoader初始化
Common&Catalina&Shared ClassLoader
- Bootstrap.init()初始化Tomcat的類加載器。
- Bootstrap的initClassLoaders()初始化Common、Catalina、Shared的類加載器。
- Common ClassLoader的父加載器是AppClassLoader。
- Catalina ClassLoader = Common ClassLoader。
- Shared ClassLoader = Common ClassLoader。
public final class Bootstrap {
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
// 初始化Bootstrap
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
}
}
public void init() throws Exception {
// 初始化Tomcat的類加載器
initClassLoaders();
}
private void initClassLoaders() {
try {
// 創建commonLoader并且未指定父節點,默認為
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
// 創建CatalinaLoader并且指定parent為commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
// 創建SharedLoader并且指定parent為commonLoader
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
}
ClassLoader創建過程
- CatalinaProperties的getProperty方法加載配置conf/Catalina/catalina.properties。
- catalina.properties的配置文件內容中:
common.loader="${catalina.base}/lib","${catalina.base}/lib/.jar","${catalina.home}/lib","${catalina.home}/lib/.jar"。
- catalina.properties的配置文件內容中:
- catalina.properties的配置文件內容中:server.loader=,返回common classLoader。
- catalina.properties的配置文件內容中:shared.loader=,返回common classLoader。
public final class Bootstrap {
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
// common.loader配置jar路徑,負責加載${catalina.base}/lib和{catalina.home}/lib
// server.loader和shared.loader配置路徑為空,所以返回parent即common classloader
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(
new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(
new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(
new Repository(repository, RepositoryType.DIR));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
}
catalina.properties配置解析
- 解析tomcat目錄下的conf/Catalina/catalina.properties配置文件。
- server.loader和shared.loader的值為空
- common.loader="${catalina.base}/lib","${catalina.base}/lib/.jar","${catalina.home}/lib","${catalina.home}/lib/.jar"
public class CatalinaProperties {
private static Properties properties = null;
static {
loadProperties();
}
public static String getProperty(String name) {
return properties.getProperty(name);
}
private static void loadProperties() {
InputStream is = null;
if (is == null) {
try {
// conf/Catalina/catalina.properties
// common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
// server.loader=
// shared.loader=
File home = new File(Bootstrap.getCatalinaBase());
File conf = new File(home, "conf");
File propsFile = new File(conf, "catalina.properties");
is = new FileInputStream(propsFile);
} catch (Throwable t) {
}
}
if (is != null) {
try {
properties = new Properties();
properties.load(is);
} catch (Throwable t) {
} finally {
try {
is.close();
} catch (IOException ioe) {
}
}
}
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String name = (String) enumeration.nextElement();
String value = properties.getProperty(name);
if (value != null) {
System.setProperty(name, value);
}
}
}
}
ClassLoaderFactory的ClassLoader工廠
- 加載repositories對應的jar目錄
- new URLClassLoader(array)創建沒傳parent的ClassLoader,這種情況下父加載器是AppClassLoader。
- new URLClassLoader(array, parent)創建以parent作為父加載器的ClassLoader。
public final class ClassLoaderFactory {
public static ClassLoader createClassLoader(List<Repository> repositories,
final ClassLoader parent)
throws Exception {
Set<URL> set = new LinkedHashSet<>();
if (repositories != null) {
for (Repository repository : repositories) {
if (repository.getType() == RepositoryType.URL) {
URL url = buildClassLoaderUrl(repository.getLocation());
set.add(url);
} else if (repository.getType() == RepositoryType.DIR) {
File directory = new File(repository.getLocation());
URL url = buildClassLoaderUrl(directory);
set.add(url);
} else if (repository.getType() == RepositoryType.JAR) {
File file=new File(repository.getLocation());
file = file.getCanonicalFile();
URL url = buildClassLoaderUrl(file);
set.add(url);
} else if (repository.getType() == RepositoryType.GLOB) {
File directory=new File(repository.getLocation());
directory = directory.getCanonicalFile();
String filenames[] = directory.list();
if (filenames == null) {
continue;
}
for (int j = 0; j < filenames.length; j++) {
String filename = filenames[j].toLowerCase(Locale.ENGLISH);
File file = new File(directory, filenames[j]);
file = file.getCanonicalFile();
URL url = buildClassLoaderUrl(file);
set.add(url);
}
}
}
}
final URL[] array = set.toArray(new URL[set.size()]);
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}
}
URLClassLoader的實現
URLClassLoader
說明:
- URLClassLoder是JVM當中的實現方式。
- URLClassLoder繼承SecureClassLoader。
- SecureClassLoader繼承ClassLoader。
public class URLClassLoader extends SecureClassLoader implements Closeable {
public URLClassLoader(URL[] urls) {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
ucp = new URLClassPath(urls, acc);
}
public URLClassLoader(URL[] urls, ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
ucp = new URLClassPath(urls, acc);
}
}
public class SecureClassLoader extends ClassLoader {
protected SecureClassLoader() {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
initialized = true;
}
}
抽象類ClassLoader
- ClassLoader()方法使用getSystemClassLoader作為parent。
- getSystemClassLoader()返回sun.misc.Launcher.getLauncher().getClassLoader()。
- sun.misc.Launcher.getLauncher().getClassLoader()是。AppClassLoader對象
public abstract class ClassLoader {
private final ClassLoader parent;
private static ClassLoader scl;
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
}
}
sclSet = true;
}
}
}
Launcher
- Launcher.AppClassLoader.getAppClassLoader(var1)返回AppClassLoader對象。
public class Launcher {
private static Launcher launcher = new Launcher();
private ClassLoader loader;
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
}
}
}
Webapp Classloader
整個Class loader的傳遞過程如下,證明Webapp classLoader的parent節點。
StandardEngine通過xml解析獲得了Catalina的class loader。
StandardHost通過xml解析獲得了StandardEngine的class loader。
StandardContext通過getParentClassLoader獲得了StandardHost的class loader。
設置Bootstrap的common、catalina、shared等Loader
Catalina的parent設置
- Bootstrap通過反射調用Catalina的setParentClassLoader設置Catalina的parentClassLoader為sharedLoader。
public final class Bootstrap {
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
public void init() throws Exception {
initClassLoaders();
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
}
StandardEingine的parent設置
- StandardEingine的parentClassLoder設置為Catalina當中的parentClassLoader。
- Catalina當中的parentClassLoader是sharedLoader。
- StandardEingine通過如下方式注入parentClassLoader: digester.addRule("Server/Service/Engine,
new SetParentClassLoaderRule(parentClassLoader));
- StandardEingine通過如下方式注入parentClassLoader: digester.addRule("Server/Service/Engine,
public class Catalina {
protected ClassLoader parentClassLoader =
Catalina.class.getClassLoader();
public void setParentClassLoader(ClassLoader parentClassLoader) {
this.parentClassLoader = parentClassLoader;
}
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null) {
return (parentClassLoader);
}
return ClassLoader.getSystemClassLoader();
}
// Engine的解析規則
protected Digester createStartDigester() {
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
}
StandardHost的parent設置
- StandardHost通過StandardEngine的getParentClassLoader獲取ClassLoader。
- StandardHost的setParentClassLoader注入StandardEngine的parentClassLoader。
- StandardHost的parentClassLoader為sharedLoader。
public class HostRuleSet extends RuleSetBase {
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
// Host的XML的解析規則
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
}
}
public class CopyParentClassLoaderRule extends Rule {
public CopyParentClassLoaderRule() {
}
@Override
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
if (digester.getLogger().isDebugEnabled())
digester.getLogger().debug("Copying parent class loader");
Container child = (Container) digester.peek(0);
Object parent = digester.peek(1);
Method method =
parent.getClass().getMethod("getParentClassLoader", new Class[0]);
ClassLoader classLoader =
(ClassLoader) method.invoke(parent, new Object[0]);
child.setParentClassLoader(classLoader);
}
}
StandardContext的parent設置
- StandardContext通過parent.getParentClassLoader()返回parentClassLoader。
- StandardContext的parent是StandardHost對象。
- StandardHost.getParentClassLoader()返回StandardHost的sharedLoader。
public class StandardContext extends ContainerBase
implements Context, NotificationEmitter {
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null)
return (parentClassLoader);
if (getPrivileged()) {
return this.getClass().getClassLoader();
} else if (parent != null) {
// parent是StandardHost
return (parent.getParentClassLoader());
}
return (ClassLoader.getSystemClassLoader());
}
}
類加載過程雙親委派
WebappClassLoader
- WebappClassLoader繼承自WebappClassLoaderBase。
- 核心實現在于WebappClassLoaderBase類。
public class WebappClassLoader extends WebappClassLoaderBase {
public WebappClassLoader() {
super();
}
public WebappClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public WebappClassLoader copyWithoutTransformers() {
WebappClassLoader result = new WebappClassLoader(getParent());
super.copyStateWithoutTransformers(result);
try {
result.start();
} catch (LifecycleException e) {
throw new IllegalStateException(e);
}
return result;
}
@Override
protected Object getClassLoadingLock(String className) {
return this;
}
}
WebappClassLoaderBase
- 調用 findLocaledClass0 從 resourceEntries 中判斷 class 是否已經加載 OK。
- 調用 findLoadedClass(內部調用一個 native 方法) 直接查看對應的 WebappClassLoader 是否已經加載過。
- 調用 binaryNameToPath 判斷是否 當前 class 是屬于 J2SE 范圍中的, 若是的則直接通過 ExtClassLoader, BootstrapClassLoader 進行加載 (這里是雙親委派)。
- 在設置 JVM 權限校驗的情況下, 調用 securityManager 來進行權限的校驗(當前類是否有權限加載這個類, 默認的權限配置文件是 ${catalina.base}/conf/catalina.policy)。
- 判斷是否設置了雙親委派機制 或 當前 WebappClassLoader 是否能加載這個 class (通過 filter(name) 來決定), 將最終的值賦值給 delegateLoad。
- 根據上一步中的 delegateLoad 來決定是否用 WebappClassloader.parent(也就是 sharedClassLoader) 來進行加載, 若加載成功, 則直接返回。
- 上一步若未加載成功, 則調用 WebappClassloader.findClass(name) 來進行加載。
- 若上一還是沒有加載成功, 則通過 parent 調用 Class.forName 來進行加載。
- 若還沒加載成功的話, 那就直接拋異常。
加載過程
public abstract class WebappClassLoaderBase extends URLClassLoader
implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
public Class<?> loadClass(String name) throws ClassNotFoundException {
return (loadClass(name, false));
}
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
//首先調用findLoaderClass0() 方法檢查WebappClassLoader中是否加載過此類
// WebappClassLoader 加載過的類都存放在 resourceEntries 緩存中。
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 調用 findLoadedClass(內部調用一個 native 方法)
// 直接查看對應的 WebappClassLoader 是否已經加載過
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 調用 binaryNameToPath 判斷是否 當前 class 是屬于 J2SE 范圍中的,
// 若是的則直接通過 ExtClassLoader, BootstrapClassLoader 進行加載
// (這里是雙親委派)
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
} catch (Throwable t) {
tryLoadingFromJavaseLoader = true;
}
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// 判斷是否需要委托給父類加載器進行加載,
// delegate屬性默認為false,那么delegatedLoad的值就取決于filter的返回值了
// filter中是優先加載tomcat的lib下的class文件
// filter方法中根據包名來判斷是否需要進行委托加載,
// 默認情況下會返回false.因此delegatedLoad為false
boolean delegateLoad = delegate || filter(name, true);
// 因為delegatedLoad為false,那么此時不會委托父加載器去加載,
// 這里其實是沒有遵循parent-first的加載機制。
if (delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// 調用findClass方法在webapp級別進行加載
try {
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 如果還是沒有加載到類,并且不采用委托機制的話,則通過父類加載器去加載
if (!delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
protected Class<?> findLoadedClass0(String name) {
String path = binaryNameToPath(name, true);
ResourceEntry entry = resourceEntries.get(path);
if (entry != null) {
return entry.loadedClass;
}
return null;
}
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
}
參考文章
違反ClassLoader雙親委派機制三部曲第二部——Tomcat類加載機制
Tomcat 源碼分析 WebappClassLoader 分析 (基于8.0.5)
招聘信息
【招賢納士】
歡迎熱愛技術、熱愛生活的你和我成為同事,和貝貝共同成長。
貝貝集團誠招算法、大數據、BI、Java、PHP、android、iOS、測試、運維、DBA等人才,有意可投遞zhi.wang@beibei.com。
貝貝集團創建于2011年,旗下擁有貝貝網、貝店、貝貸等平臺,致力于成為全球領先的家庭消費平臺。
貝貝創始團隊來自阿里巴巴,先后獲得IDG資本、高榕資本、今日資本、新天域資本、北極光等數億美金的風險投資。
公司地址:杭州市江干區普盛巷9號東谷創業園(上下班有多趟班車)