類裝載器
JDK中提供了3種不同的類加載器:啓動類裝載器,擴展類裝載器和系統類裝載器。引導類裝載器,用於引導啓動JAVA虛擬機,當執行一個JAVA程序時,就會啓動引導類裝載器,它是使用本地代碼來實現的,會裝載%JAVA_HOME%\\jre\lib\rt.jar,它是所有類裝載器類的父裝載器。擴展類裝載器負責載入標準擴展目錄中的類,其搜索路徑是%JAVA_HOME%\jre\lib\ext,只需要將打包好的jar文件放入這個目錄就可以了,給開發提供了很大的便利性。系統類裝載器是默認的裝載器,其搜索路徑是classpath。
JVM到底使用的是哪一個類裝載器,取決於類裝載器的代理模式。每當需要裝載一個類的時候,會首先調用系統類裝載器,但是系統類裝載器並不會立即裝載,而是將其交給父裝載器:擴展類裝載器,擴展類裝載器其將交給引導類裝載器,引導類裝載器沒有父裝載器,它會嘗試裝載這個類,如果找不到這個類,會交給擴展類裝載器,如果擴展類裝載器還是沒有找到,會交給系統類裝載器,如果系統類裝載器還是沒有找到這個類,則會拋出java.lang.ClassNotFoundException異常。代理模式主要是爲了解決類裝載的安全問題。例如:對於自定類的java.lang.Object類,永遠得不到裝載,除非,rt.jar中確實沒有這個類。
tomcat也提供了幾種不同的類裝載器用於加載不同位置的jar包和class文件,特別是Context容器需要有一個單獨的類裝載器,因爲不同應用可能有相同的類,如果用同一個類裝載器去裝載,就不知道該加載哪個應用裏面的類了。這些類裝載器之間的關係如下圖所示:
系統類裝載器
tomcat的系統類裝載器和JDK的系統類裝載器有點不同的地方是搜索路徑並不相同,在catalina.bat中做了如下修改:
rem Add on extra jar file to CLASSPATH
rem Note that there are no quotes as we do not want to introduce random
rem quotes into the CLASSPATH
if "%CLASSPATH%" == "" goto emptyClasspath
set "CLASSPATH=%CLASSPATH%;"
:emptyClasspath
set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"
rem Add tomcat-juli.jar to classpath
rem tomcat-juli.jar can be over-ridden per instance
if not exist "%CATALINA_BASE%\bin\tomcat-juli.jar" goto juliClasspathHome
set "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"
先將classpath清空,因爲classpath中可能有tomcat啓動相關的類會影響tomcat的正常啓動。然後將bootstrap.jar和tomcat-juli.jar加入classpath中,在catalina.bat中調用了Bootstrap類的main方法,這裏系統類裝載器會裝載Bootstrap類,Bootstrap類用到的Catalina類也是由系統類裝載器裝載的。public void init() throws Exception{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
...
}
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);
}
}
Common Class Loader
private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<Repository>();
StringTokenizer tokenizer = new StringTokenizer(value, ",");
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken().trim();
if (repository.length() == 0) {
continue;
}
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
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));
}
}
ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories, parent);
...
return classLoader;
}
(1)、從bootstrap.jar包可以找到catalina.properties文件:common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar,很明顯這個類裝載器搜索路徑就是${catalina.home}/lib和${catalina.home}/lib/*.jar,之所以叫common class loader,是因爲它加載每個應用要用到的公共jar包和class文件;Catalina Class Loader
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
common class loader是以common class loader爲父裝載器的,因此其搜索路徑和common class loader一樣。catalina class loader創建好後,在init方法中隨即調用了Thread.currentThread().setContextClassLoader(catalinaLoader);將其設置爲當前線程的類裝載器Shared Class Loader
Webapp Class Loader
tomcat的一個service除了容器和連接器外還有很多組件,比如sessionManager,logger,loader等,這個類裝載器是以組件的形式附着在每個容器上的,Engine和Host的這兩個容器的loader組件爲null,context裏面是有值的,看看context的startInternal方法:
protected synchronized void startInternal() throws LifecycleException {
...
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
...
// Binding thread
ClassLoader oldCCL = bindThread();
try {
if (ok) {
// Start our subordinate components, if any
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
// since the loader just started, the webapp classloader is now
// created.
// By calling unbindThread and bindThread in a row, we setup the
// current Thread CCL to be the webapp classloader
unbindThread(oldCCL);
oldCCL = bindThread();
}
...
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
}
很明顯,context在啓動的時候創建了一個loader組件,webapploader正是loader的實現類,這個類並不是最終的類裝載器,在這個類裏面有一個webappclassloader類型的字段叫classloader,這個classloader的創建是在loader組件的start方法中完成的 protected void startInternal() throws LifecycleException {
...
// Construct a class loader based on our current repositories list
try {
classLoader = createClassLoader();
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);
if (container instanceof StandardContext) {
classLoader.setAntiJARLocking(
((StandardContext) container).getAntiJARLocking());
classLoader.setClearReferencesStatic(
((StandardContext) container).getClearReferencesStatic());
classLoader.setClearReferencesStopThreads(
((StandardContext) container).getClearReferencesStopThreads());
classLoader.setClearReferencesStopTimerThreads(
((StandardContext) container).getClearReferencesStopTimerThreads());
classLoader.setClearReferencesHttpClientKeepAliveThread(
((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
}
for (int i = 0; i < repositories.length; i++) {
classLoader.addRepository(repositories[i]);
}
// Configure our repositories
setRepositories();
setClassPath();
setPermissions();
((Lifecycle) classLoader).start();
...
} catch (Throwable t) {
...
}
...
}
(1)、在createClassLoader方法中通過反射實例化了org.apache.catalina.loader.WebappClassLoader這個類,並調用了它的setParentClassLoader設置其父裝載器爲standardClassLoader private WebappClassLoader createClassLoader()
throws Exception {
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = container.getParentClassLoader();
}
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);
return classLoader;
}
(2)、爲webappclassloader添加倉庫(倉庫表示類裝載器會在哪些路徑搜索類)private void setRepositories() throws IOException {
...
// Setting up the class repository (/WEB-INF/classes), if it exists
String classesPath = "/WEB-INF/classes";
...
// Adding the repository to the class loader
classLoader.addRepository(classesPath + "/", classRepository);
// Setting up the JAR repository (/WEB-INF/lib), if it exists
String libPath = "/WEB-INF/lib";
...
// Looking up directory /WEB-INF/lib in the context
NamingEnumeration<NameClassPair> enumeration = libDir.list("");
while (enumeration.hasMoreElements()) {
NameClassPair ncPair = enumeration.nextElement();
String filename = libPath + "/" + ncPair.getName();
if (!filename.endsWith(".jar"))
continue;
...
try {
JarFile jarFile = new JarFile(destFile);
classLoader.addJar(filename, jarFile, destFile);
} catch (Exception ex) {
...
}
...
}
}
(3)、爲類裝載器設置權限,這裏Globals.IS_SECURITY_ENABLED值爲false,表示安全機制未打開,直接返回 protected static final String[] triggers = {
"javax.servlet.Servlet", "javax.el.Expression" // Servlet API
};
WebappClassLoader裝載類
public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
...
// 先檢查本地緩存
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 如果本地緩存沒有,則檢查上一級緩存
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 如果兩個緩存都沒有,則使用系統的類裝載器進行裝載,防止Web應用程序中的類覆蓋J2EE的類
try {
clazz = system.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 如果啓用了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;
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name);
// 若打開了delegateLoad標誌位,調用父裝載器來加載。如果父裝載器爲null,使用系統類裝載器裝載
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = Class.forName(name, false, loader);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// 從本地倉庫中載入相關類
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
}
// 若當前倉庫中沒有需要的類,且delegateLoad標誌位關閉,則使用父裝載器。若父裝載器爲null,使用系統類裝載器來裝載
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = Class.forName(name, false, loader);
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);
}
整個思路是:先到緩存中獲取,如果緩存中有直接返回,否則根據delegateLoad採取不同的加載方式。如果未啓用這個標誌:先本地倉庫加載再父裝載器或者系統類裝載器裝載;如果啓用了這個標誌:直接由父裝載器或者系統類裝載器裝載。