目錄
- Web應用程序類加載器(WebappClassLoader)
- JSP類加載器(JasperLoader)
眾所周知,Java中默認的類加載器是以父子關系存在的,實現了雙親委派機制進行類的加載,在前文中,我們提到了,雙親委派機制的設計是為了保證類的唯一性,這意味著在同一個JVM中是不能加載相同類庫的不同版本的類。
然而與許多服務器應用程序一樣,Tomcat 允許容器的不同部分以及在容器上運行的不同Web應用程序可以訪問的各種不同版本的類庫,這就要求Tomcat必須打破這種雙親委派機制,通過實現自定義的類加載器(即實現了java.lang.ClassLoader)進行類的加載。下面,就讓我們來看看Tomcat類加載原理是怎樣的。
Tomcat中有兩個最重要的類加載器,第一個便是負責Web應用程序類加載的WebappClassLoader,另一個便是JSP Servlet類加載器`JasperLoader。
Web應用程序類加載器(WebappClassLoader)
上代碼:
public class WebappClassLoader extends WebappClassLoaderBase {
public WebappClassLoader() {
super();
}
public WebappClassLoader(ClassLoader parent) {
super(parent);
}
...
}
我們來看看WebappClassLoader繼承的WebappClassLoaderBase中實現的類加載方法loadClass
public abstract class WebappClassLoaderBase extends URLClassLoader
implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
//... 省略不需要關注的代碼
protected WebappClassLoaderBase() {
super(new URL[0]);
// 獲取當前WebappClassLoader的父加載器系統類加載器
ClassLoader p = getParent();
if (p == null) {
p = getSystemClassLoader();
}
this.parent = p;
// javaseClassLoader變量經過以下代碼的執行,
// 得到的是擴展類加載器(ExtClassLoader)
ClassLoader j = String.class.getClassLoader();
if (j == null) {
j = getSystemClassLoader();
while (j.getParent() != null) {
j = j.getParent();
}
}
this.javaseClassLoader = j;
securityManager = System.getSecurityManager();
if (securityManager != null) {
refreshPolicy();
}
}
//...省略不需要關注的代碼
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled()) {
log.debug("loadClass(" + name + ", " + resolve + ")");
}
Class<?> clazz = null;
// Web應用程序停止狀態時,不允許加載新的類
checkStateForClassLoading(name);
// 如果之前加載過該類,就可以從Web應用程序類加載器本地類緩存中查找,
// 如果找到說明WebappClassLoader之前已經加載過這個類
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// Web應用程序本地類緩存中沒有,可以從系統類加載器緩存中查找,
// 如果找到說明AppClassLoader之前已經加載過這個類
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// 將類似java.lang.String這樣的類名這樣轉換成java/lang/String
// 這樣的資源文件名
String resourceName = binaryNameToPath(name, false);
// 獲取引導類加載器(BootstrapClassLoader)
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// 引導類加載器根據轉換后的類名獲取資源url,如果url不為空,就說明找到要加載的類
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
tryLoadingFromJavaseLoader = true;
}
// 首先,從擴展類加載器(ExtClassLoader)加載,防止Java核心API庫被Web應用程序類隨意篡改
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// 當使用安全管理器時,允許訪問這個類
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = sm.getString("webappClassLoader.restrictedPackage", name);
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
/*
* 如果Web應用程序類加載器配置為,<Loader delegate="true"/> 或者滿足下列條件的類:
* 當前類屬于以下這些jar包中:
* annotations-api.jar — Common Annotations 1.2 類。
* catalina.jar — Tomcat 的 Catalina servlet 容器部分的實現。
* catalina-ant.jar — 可選。用于使用 Manager Web 應用程序的 Tomcat Catalina Ant 任務。
* catalina-ha.jar — 可選。提供基于 Tribes 構建的會話集群功能的高可用性包。
* catalina-storeconfig.jar — 可選。從當前狀態生成 XML 配置文件。
* catalina-tribes.jar — 可選。高可用性包使用的組通信包。
* ecj-*.jar — 可選。Eclipse JDT Java 編譯器用于將 JSP 編譯為 Servlet。
* el-api.jar — 可選。EL 3.0 API。
* jasper.jar — 可選。Tomcat Jasper JSP 編譯器和運行時。
* jasper-el.jar — 可選。Tomcat EL 實現。
* jaspic-api.jar — JASPIC 1.1 API。
* jsp-api.jar — 可選。JSP 2.3 API。
* servlet-api.jar — Java Servlet 3.1 API。
* tomcat-api.jar — Tomcat 定義的幾個接口。
* tomcat-coyote.jar — Tomcat 連接器和實用程序類。
* tomcat-dbcp.jar — 可選。基于 Apache Commons Pool 2 和 Apache Commons DBCP 2 的
* 包重命名副本的數據庫連接池實現。
* tomcat-i18n-**.jar — 包含其他語言資源包的可選 JAR。由于默認包也包含在每個單獨的JAR
* 中,如果不需要消息國際化,可以安全地刪除它們。
* tomcat-jdbc.jar — 可選。另一種數據庫連接池實現,稱為 Tomcat JDBC 池。有關詳細信息,請參閱 文檔。
* tomcat-jni.jar — 提供與 Tomcat Native 庫的集成。
* tomcat-util.jar — Apache Tomcat 的各種組件使用的通用類。
* tomcat-util-scan.jar — 提供 Tomcat 使用的類掃描功能。
* tomcat-websocket.jar — 可選。Java WebSocket 1.1 實現
* websocket-api.jar — 可選。Java WebSocket 1.1 API
*
* 此處的filter方法,實際上tomcat官方將filter類加載過濾條件,看作是一種類加載器,
* 將其取名為CommonClassLoader
*/
boolean delegateLoad = delegate || filter(name, true);
// 如果ExtClassLoader沒有獲取到,說明是非JRE核心類,那么就從系統類加載器(也稱AppClassLoader
// 應用程序類加載器)加載
if (delegateLoad) {
if (log.isDebugEnabled()) {
log.debug(" Delegating to parent classloader1 " + parent);
}
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
}
}
// 從Web應用程序的類加載器(也就是WebappClassLoader)中加載類。Web應用程序的類加載器是
// 一個特殊的類加載器,它負責從Web應用程序的本地庫中加載類
if (log.isDebugEnabled()) {
log.debug(" Searching local repositories");
}
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) {
if (log.isDebugEnabled()) {
log.debug(" Delegating to parent classloader at end: " + parent);
}
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);
}
//...省略不需要關注的代碼
}
綜上所述,我們得出WebappClassLoader類加載器打破了雙親委派機制,自定義類加載類的順序:
- 擴展類加載器(ExtClassLoader)加載
- Web應用程序類加載器(WebappClassLoader)
- 系統類加載器類(AppClassLoader)
- 公共類加載器類(CommonClassLoader)
如果Web應用程序類加載器配置為,,也就是WebappClassLoaderBase類的變量delegate=true時,則類加載順序變為:
- 擴展類加載器(ExtClassLoader)加載
- 系統類加載器類(AppClassLoader)
- 公共類加載器類(CommonClassLoader)
- Web應用程序類加載器(WebappClassLoader)
JSP類加載器(JasperLoader)
上代碼:
public class JasperLoader extends URLClassLoader {
private final PermissionCollection permissionCollection;
private final SecurityManager securityManager;
// JSP類加載器的父加載器是Web應用程序類加載器(WebappClassLoader)
public JasperLoader(URL[] urls, ClassLoader parent,
PermissionCollection permissionCollection) {
super(urls, parent);
this.permissionCollection = permissionCollection;
this.securityManager = System.getSecurityManager();
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
@Override
public synchronized Class<?> loadClass(final String name, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = null;
// 從JVM的類緩存中查找
clazz = findLoadedClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// 當使用SecurityManager安全管理器時,允許訪問訪類
if (securityManager != null) {
int dot = name.lastIndexOf('.');
if (dot >= 0) {
try {
// Do not call the security manager since by default, we grant that package.
if (!"org.apache.jasper.runtime".equalsIgnoreCase(name.substring(0,dot))){
securityManager.checkPackageAccess(name.substring(0,dot));
}
} catch (SecurityException se) {
String error = "Security Violation, attempt to use " +
"Restricted Class: " + name;
se.printStackTrace();
throw new ClassNotFoundException(error);
}
}
}
// 如果類名不是以org.apache.jsp包名開頭的,則采用WebappClassLoader加載
if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) {
// Class is not in org.apache.jsp, therefore, have our
// parent load it
clazz = getParent().loadClass(name);
if( resolve ) {
resolveClass(clazz);
}
return clazz;
}
// 如果是org.apache.jsp包名開頭JSP類,就調用父類URLClassLoader的findClass方法
// 動態加載類文件,解析成Class類,返回給調用方
return findClass(name);
}
}
下面是URLClassLoader的findClass方法,具體實現:
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 {
// 解析類的字節碼文件生成Class類對象
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;
}
從源碼中我們可以看到,JSP類加載原理是,先從JVM類緩存中(也就是Bootstrap類加載器加載的類)加載,如果不是核心類庫的類,就從Web應用程序類加載器WebappClassLoader中加載,如果還未找到,就說明是jsp類,則通過動態解析jsp類文件獲得要加載的類。
經過上面兩個Tomcat核心類加載器的剖析,我們也就知道了Tomcat類的加載原理了。
下面我們來總結一下:Tomcat會為每個Web應用程序創建一個WebappClassLoader類加載器進行類的加載,不同的類加載器實例加載的類是會被認為是不同的類,即使它們的類名相同,這樣的話就可以實現在同一個JVM下,允許Tomcat容器的不同部分以及在容器上運行的不同Web應用程序可以訪問的各種不同版本的類庫。
針對JSP類,會由專門的JSP類加載器(JasperLoader)進行加載,該加載器會針對JSP類在每次加載時都會解析類文件,Tomcat容器會啟動一個后臺線程,定時檢測JSP類文件的變化,及時更新類文件,這樣就實現JSP文件的熱加載功能。
以上就是源碼剖析Tomcat類的加載原理的詳細內容,更多關于Tomcat類加載的資料請關注其它相關文章!






