本文接下來分析tomcat的類載入器,tomcat需要實(shí)現(xiàn)一個自定義的載入器,而不能使用系統(tǒng)類載入器
(1)限制serlvet訪問當(dāng)前運(yùn)行的java虛擬機(jī)中環(huán)境變量CLASSPATH指明的路徑下的所有類和庫,而只允許載入WEB-INF/class目錄及其子目錄下的類,和從部署的庫到WEB-INF/lib目錄載入類
(2)提供自動重載的功能,即當(dāng)WEB-INF/class目錄或WEB-INF/lib目錄下的類發(fā)生變化時,Web應(yīng)用程序會重新載入這些類
我們先來回顧一下java的類載入器,當(dāng)我們創(chuàng)建java類的實(shí)例時,都必須先將類載入到內(nèi)存中,java虛擬機(jī)使用類載入器來載入需要的類
JVM使用三種類型的類載入器來載入所需要的類,分別為引導(dǎo)類載入器(bootstrap class loader)、擴(kuò)展類載入器(extensions class loader)和系統(tǒng)類載入器(system class loader)
- 引導(dǎo)類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實(shí)現(xiàn)的,并不繼承自 java.lang.ClassLoader。
- 擴(kuò)展類加載器(extensions class loader):它用來加載 Java 的擴(kuò)展庫。Java 虛擬機(jī)的實(shí)現(xiàn)會提供一個擴(kuò)展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
- 系統(tǒng)類加載器(system class loader):它根據(jù) Java 應(yīng)用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應(yīng)用的類都是由它來完成加載的??梢酝ㄟ^ ClassLoader.getSystemClassLoader()來獲取它。
JVM采用一種代理模型的類載入機(jī)制,可以解決類載入過程中的安全性問題;每當(dāng)系統(tǒng)需要載入一個類的時候,會首先調(diào)用系統(tǒng)類載入器,而系統(tǒng)類載入器將載入類的任務(wù)委派給其父載入器,即擴(kuò)展類載入器,同樣擴(kuò)展類載入器將載入類的任務(wù)委派給其父載入器,即引導(dǎo)類載入器;因此引導(dǎo)類載入器會首先執(zhí)行載入某個類的任務(wù),如果引導(dǎo)類載入器找不到需要載入的類,那么擴(kuò)展類載入器嘗試載入該類,如果擴(kuò)展類載入器也找不到該類,就輪到系統(tǒng)類載入器繼續(xù)執(zhí)行載入任務(wù)。如果系統(tǒng)類載入器還是找不到該類,則會拋出java.lang.ClassNotFoundException異常
Tomcat中的載入器是指Web應(yīng)用程序載入器,而不僅僅指類載入器,載入器必須實(shí)現(xiàn)org.apache.catalina.Loader接口
public
interface
Loader {
public
ClassLoader getClassLoader();
public
Container getContainer();
public
void
setContainer(Container container);
public
DefaultContext getDefaultContext();
public
void
setDefaultContext(DefaultContext defaultContext);
public
boolean
getDelegate();
public
void
setDelegate(
boolean
delegate);
public
String getInfo();
public
boolean
getReloadable();
public
void
setReloadable(
boolean
reloadable);
public
void
addPropertyChangeListener(PropertyChangeListener listener);
public
void
addRepository(String repository);
public
String[] findRepositories();
public
boolean
modified();
public
void
removePropertyChangeListener(PropertyChangeListener listener);
}
下面我們來具體來分析tomcat容器中 Web應(yīng)用程序載入器的具體實(shí)現(xiàn),即org.apache.catalina.loader.WebappLoader類實(shí)現(xiàn)了上面的Loader接口,負(fù)責(zé)載入Web應(yīng)用程序中所使用到的類。
WebappLoader類會創(chuàng)建org.apache.catalina.loader.WebappClassLoader類的實(shí)例作為其類載入器;
同時WebappLoader類也實(shí)現(xiàn)了org.apache.catalina.Lifecycle接口,可以由其相關(guān)聯(lián)的容器來啟動或關(guān)閉;
此外,WebappLoader類還實(shí)現(xiàn)了java.lang.Runnable接口,通過一個線程不斷地調(diào)用其類載入器的modified()方法。如果modified()方法返回true, WebappLoader的實(shí)例會通知其關(guān)聯(lián)的servlet容器(在這里是Context類的實(shí)例),然后由Context實(shí)例來完成類的重新載入。
當(dāng)調(diào)用WebappLoader類的start()方法時,會完成以下幾項(xiàng)重要工作:
(1)創(chuàng)建一個類載入器
(2)設(shè)置倉庫
(3)設(shè)置類路徑
(4)設(shè)置訪問權(quán)限
(5)啟動一個新線程來支持自動重載
WebappLoader類會調(diào)用其私有方法createClassLoader()方法來創(chuàng)建默認(rèn)的類載入器
/**
* Create associated classLoader.
*/
private
WebappClassLoader createClassLoader()
throws
Exception {
Class clazz
=
Class.forName(loaderClass);
WebappClassLoader classLoader
=
null
;
if
(parentClassLoader ==
null
) {
//
Will cause a ClassCast is the class does not extend WCL, but
//
this is on purpose (the exception will be caught and rethrown)
classLoader =
(WebappClassLoader) clazz.newInstance();
}
else
{
Class[] argTypes
= { ClassLoader.
class
};
Object[] args
=
{ parentClassLoader };
Constructor constr
=
clazz.getConstructor(argTypes);
classLoader
=
(WebappClassLoader) constr.newInstance(args);
}
return
classLoader;
}
String loaderClass 的默認(rèn)值為org.apache.catalina.loader.WebappClassLoader
啟動方法后面的設(shè)置倉庫、設(shè)置類路徑、設(shè)置訪問權(quán)限等都與類載入器的初始化相關(guān)
WebappLoader類支持自動重載功能,如果WEB-INF/class目錄或WEB-INF/lib目錄下的某些類被重新編譯了,那么這些類會自動重新載入,而無需重啟tomcat。WebappLoader類使用一個線程周期性地檢查每個資源的時間戳,我們可以調(diào)用setCheckInterval()方法設(shè)置間隔時間
/**
* The background thread that checks for session timeouts and shutdown.
*/
public
void
run() {
if
(debug >= 1
)
log(
"BACKGROUND THREAD Starting"
);
//
Loop until the termination semaphore is set
while
(!
threadDone) {
//
Wait for our check interval
threadSleep();
if
(!
started)
break
;
try
{
//
Perform our modification check
if
(!
classLoader.modified())
continue
;
}
catch
(Exception e) {
log(sm.getString(
"webappLoader.failModifiedCheck"
), e);
continue
;
}
//
Handle a need for reloading
notifyContext();
break
;
}
if
(debug >= 1
)
log(
"BACKGROUND THREAD Stopping"
);
}
在上面run()方法里面的循環(huán)中,首先使線程休眠一段時間,然后調(diào)用 WebappLoader實(shí)例的類載入器的 modified()方法檢查已經(jīng)載入的類是否被修改,肉沒有修改則重新執(zhí)行循環(huán);否則調(diào)用私有方法notifyContext(),通知與WebappLoader實(shí)例相關(guān)聯(lián)的Context容器重新載入相關(guān)類
/**
* Notify our Context that a reload is appropriate.
*/
private
void
notifyContext() {
WebappContextNotifier notifier
=
new
WebappContextNotifier();
(
new
Thread(notifier)).start();
}
上面方法中的WebappContextNotifier為內(nèi)部類,實(shí)現(xiàn)了Runnable接口
/**
* Private thread class to notify our associated Context that we have
* recognized the need for a reload.
*/
protected
class
WebappContextNotifier
implements
Runnable {
/**
* Perform the requested notification.
*/
public
void
run() {
((Context) container).reload();
}
}
下面我們來分析web應(yīng)用程序中負(fù)責(zé)載入類的類載入器org.apache.catalina.loader.WebappClassLoader,該類繼承自java.net.URLClassLoader,同時實(shí)現(xiàn)了org.apache.catalina.loader.Reloader接口
public
interface
Reloader {
public
void
addRepository(String repository);
public
String[] findRepositories();
public
boolean
modified();
}
該接口用來與倉庫操作相關(guān),同時檢查web應(yīng)用程序中的某個servlet或相關(guān)的類是否被修改
每個由WebappClassLoader載入的類,都視為資源,由org.apache.catalina.loader.ResourceEntry類的實(shí)例表示,存儲了類的相關(guān)信息
public
class
ResourceEntry {
public
long
lastModified = -1
;
public
byte
[] binaryContent =
null
;
public
Class loadedClass =
null
;
public
URL source =
null
;
public
URL codeBase =
null
;
public
Manifest manifest =
null
;
public
Certificate[] certificates =
null
;
}
在WebappClassLoader類中,所有已經(jīng)緩存的類存儲在名為resourceEntries的HashMap類型的變量中,而載入失敗的類被存儲在另一個名為notFoundResources的HashMap類型的變量中
下面是WebappClassLoader的loadClass()方法的具體實(shí)現(xiàn)
public
Class loadClass(String name,
boolean
resolve)
throws
ClassNotFoundException {
if
(debug >= 2
)
log(
"loadClass(" + name + ", " + resolve + ")"
);
Class clazz
=
null
;
//
Don't load classes if class loader is stopped
if
(!
started) {
log(
"Lifecycle error : CL stopped"
);
throw
new
ClassNotFoundException(name);
}
//
(0) Check our previously loaded local class cache
clazz =
findLoadedClass0(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Returning class from cache"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
//
(0.1) Check our previously loaded class cache
clazz =
findLoadedClass(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Returning class from cache"
);
if
(resolve)
resolveClass(clazz);
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
) {
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
//
Ignore
}
//
(0.5) Permission to access this class when using a SecurityManager
if
(securityManager !=
null
) {
int
i = name.lastIndexOf('.'
);
if
(i >= 0
) {
try
{
securityManager.checkPackageAccess(name.substring(
0
,i));
}
catch
(SecurityException se) {
String error
= "Security Violation, attempt to use " +
"Restricted Class: " +
name;
System.out.println(error);
se.printStackTrace();
log(error);
throw
new
ClassNotFoundException(error);
}
}
}
boolean
delegateLoad = delegate ||
filter(name);
//
(1) Delegate to our parent if requested
if
(delegateLoad) {
if
(debug >= 3
)
log(
" Delegating to parent classloader"
);
ClassLoader loader
=
parent;
if
(loader ==
null
)
loader
=
system;
try
{
clazz
=
loader.loadClass(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Loading class from parent"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
;
}
}
//
(2) Search local repositories
if
(debug >= 3
)
log(
" Searching local repositories"
);
try
{
clazz
=
findClass(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Loading class from local repository"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
;
}
//
(3) Delegate to parent unconditionally
if
(!
delegateLoad) {
if
(debug >= 3
)
log(
" Delegating to parent classloader"
);
ClassLoader loader
=
parent;
if
(loader ==
null
)
loader
=
system;
try
{
clazz
=
loader.loadClass(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Loading class from parent"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
;
}
}
//
This class was not found
throw
new
ClassNotFoundException(name);
}
在WebappClassLoader實(shí)例載入類時,首先是檢查緩存,然后再載入指定類(如果設(shè)置了安全管理。在代理載入器載入前還要檢測類型的安全)
---------------------------------------------------------------------------?
本系列How Tomcat Works系本人原創(chuàng)?
轉(zhuǎn)載請注明出處 博客園 刺猬的溫馴?
本人郵箱: ? chenying998179 # 163.com ( #改為@ )
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

