一锦针、什么是Classloader
一個(gè)Java程序要想運(yùn)行起來(lái),首先需要經(jīng)過(guò)編譯生成 .class文件,然后創(chuàng)建一個(gè)運(yùn)行環(huán)境(jvm)來(lái)加載字節(jié)碼文件到內(nèi)存運(yùn)行,而.class 文件是怎樣被加載中jvm 中的就是Java Classloader所做的事情鸠匀。
那么.class文件什么時(shí)候會(huì)被類加載器加載到j(luò)vm中運(yùn)行那?比如執(zhí)行new操作時(shí)候猛频,當(dāng)我們使用Class.forName("包路徑+類名")狮崩,Class.forName("包路徑+類名",classloader),classloader.loadclass("包路徑+類名");時(shí)候就觸發(fā)了類加載器去類加載對(duì)應(yīng)的路徑去查找*.class,并創(chuàng)建Class對(duì)象。另外理解 cl只能加載jar里面的文件夾里面的class文件鹿寻,不能加載jar里面嵌套的jar里面的class睦柴,這個(gè)很重要
阿里巴巴長(zhǎng)期招聘Java研發(fā)工程師p6,p7,p8等上不封頂級(jí)別,有意向的可以發(fā)簡(jiǎn)歷給我,注明想去的部門和工作地點(diǎn):1064454834@qq.com
二毡熏、Java自帶的Classloader
2.1 BootstrapClassloader
引導(dǎo)類加載器坦敌,又稱啟動(dòng)類加載器,是最頂層的類加載器痢法,主要用來(lái)加載Java核心類狱窘,如rt.jar、resources.jar财搁、charsets.jar等蘸炸,Sun的JVM中,執(zhí)行java的命令中使用-Xbootclasspath選項(xiàng)或使用- D選項(xiàng)指定sun.boot.class.path系統(tǒng)屬性值可以指定附加的類尖奔,它不是 java.lang.ClassLoader的子類搭儒,而是由JVM自身實(shí)現(xiàn)的該類c 語(yǔ)言實(shí)現(xiàn),Java程序訪問(wèn)不到該加載器提茁。通過(guò)下面代碼可以查看該加載器加載了哪些jar包
public void test() {
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
}
執(zhí)行結(jié)果:
file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/classes淹禾,
寫到這里大家應(yīng)該都知道,我們并沒(méi)有在classpath里面指定這些類的路徑茴扁,為啥還是能被加載到j(luò)vm并使用起來(lái)了吧铃岔,因?yàn)檫@些是bootstarp來(lái)加載的。
2.2 ExtClassloader
擴(kuò)展類加載器峭火,主要負(fù)責(zé)加載Java的擴(kuò)展類庫(kù)毁习,默認(rèn)加載JAVA_HOME/jre/lib/ext/目下的所有jar包或者由java.ext.dirs系統(tǒng)屬性指定的jar包。放入這個(gè)目錄下的jar包對(duì)所有AppClassloader都是可見(jiàn)的(后面會(huì)知道ExtClassloader是AppClassloader的父加載器)躲胳。那么ext都是在那些地方加載類內(nèi):
System.out.println(System.getProperty("java.ext.dirs"));
/Users/zhuizhumengxiang/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
2.3 AppClassloader
系統(tǒng)類加載器蜓洪,又稱應(yīng)用加載器,本文說(shuō)的SystemClassloader和APPClassloader是一個(gè)東西坯苹,它負(fù)責(zé)在JVM啟動(dòng)時(shí)隆檀,加載來(lái)自在命令java中的-classpath或者java.class.path系統(tǒng)屬性或者 CLASSPATH操作系統(tǒng)屬性所指定的JAR類包和類路徑。調(diào)用ClassLoader.getSystemClassLoader()可以獲取該類加載器粹湃。如果沒(méi)有特別指定乾吻,則用戶自定義的任何類加載器都將該類加載器作為它的父加載器,這點(diǎn)通過(guò)ClassLoader的無(wú)參構(gòu)造函數(shù)可以知道如下:
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
執(zhí)行以下代碼即可獲得classpath加載路徑:
System.out.println(System.getProperty("java.class.path"));
2.4 三種加載器聯(lián)系
用一張圖來(lái)表示三張圖的關(guān)系如下:
用戶自定義的無(wú)參加載器的父類加載器默認(rèn)是AppClassloader加載器铣缠,而AppClassloader加載器的父加載器是ExtClassloader,通過(guò)下面代碼可以驗(yàn)證:
ClassLoader.getSystemClassLoader().getParent()
一般我們都認(rèn)為ExtClassloader的父類加載器是BootStarpClassloader,但是其實(shí)他們之間根本是沒(méi)有父子關(guān)系的卸耘,只是在ExtClassloader找不到要加載類時(shí)候會(huì)去委托BootStrap加載器去加載。
通過(guò)如下代碼可以知道父加載器為null
ClassLoader.getSystemClassLoader().getParent().getParent()
2.5 類加載器原理
Java類加載器使用的是委托機(jī)制择葡,也就是子類加載器在加載一個(gè)類時(shí)候會(huì)讓父類來(lái)加載,那么問(wèn)題來(lái)了纯丸,為啥使用這種方式那?因?yàn)檫@樣可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類的時(shí)候静袖,就沒(méi)有必要子ClassLoader再加載一次觉鼻。考慮到安全因素队橙,我們?cè)囅胍幌伦钩拢绻皇褂眠@種委托模式,那我們就可以隨時(shí)使用自定義的String來(lái)動(dòng)態(tài)替代java核心api中定義的類型捐康,這樣會(huì)存在非常大的安全隱患仇矾,而雙親委托的方式,就可以避免這種情況解总,因?yàn)镾tring已經(jīng)在啟動(dòng)時(shí)就被引導(dǎo)類加載器(Bootstrcp ClassLoader)加載贮匕,所以用戶自定義的ClassLoader永遠(yuǎn)也無(wú)法加載一個(gè)自己寫的String,除非你改變JDK中ClassLoader搜索類的默認(rèn)算法花枫。下面我們從源碼看如何實(shí)現(xiàn)委托機(jī)制:
protected Class<?> loadClass(Stringname,boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先從jvm緩存查找該類
Class c = findLoadedClass(name); (1)
if (c ==null) {
longt0 = System.nanoTime();
try { //然后委托給父類加載器進(jìn)行加載
if (parent !=null) {
c = parent.loadClass(name,false); (2)
} else { //如果父類加載器為null,則委托給BootStrap加載器加載
c = findBootstrapClassOrNull(name); (3)
}
} catch (ClassNotFoundExceptione) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c ==null) {
// 若仍然沒(méi)有找到則調(diào)用findclass查找
// to find the class.
longt1 = System.nanoTime();
c = findClass(name); (4)
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 -t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c); //鏈接粗合,文件格式,字節(jié)碼驗(yàn)證乌昔,符號(hào)引用轉(zhuǎn)為直接引用等
}
returnc;
}
}
分析代碼知道首先會(huì)執(zhí)行(1)從jvm緩存查找該類隙疚,如何該類之前被加載過(guò),則直接從jvm緩存返回該類磕道,否者看當(dāng)前類加載器是否有父加載器供屉,如果有的話則委托為父類加載器進(jìn)行加載(2),否者調(diào)用(3)委托為BootStrapClassloader進(jìn)行加載溺蕉,如果還是沒(méi)有找到伶丐,則調(diào)用當(dāng)前Classloader的findclass方法進(jìn)行查找。
從上面源碼知道要想修改類加載委托機(jī)制疯特,實(shí)現(xiàn)自己的載入策略 可以通過(guò)覆蓋ClassLoader的findClass方法或者覆蓋loadClass方法來(lái)實(shí)現(xiàn)哗魂。
2.6 Java中如何構(gòu)造三種類加載器的結(jié)構(gòu)
下面從源碼來(lái)分析下JVM是如何構(gòu)建內(nèi)置classloader的,具體是rt.jar包里面sun.misc.Launcher類:
public Launcher()
{
ExtClassLoader localExtClassLoader;
try
{ //首先創(chuàng)建了ExtClassLoader
localExtClassLoader = ExtClassLoader.getExtClassLoader();
}
catch (IOException localIOException1)
{
throw new InternalError("Could not create extension class loader");
}
try
{ //然后以ExtClassloader作為父加載器創(chuàng)建了AppClassLoader
this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
}
catch (IOException localIOException2)
{
throw new InternalError("Could not create application class loader");
} //這個(gè)是個(gè)特殊的加載器后面會(huì)講到漓雅,這里只需要知道默認(rèn)下線程上下文加載器為appclassloader
Thread.currentThread().setContextClassLoader(this.loader);
................
}
下面看下ExtClassLoader.getExtClassLoader()的代碼
public static ExtClassLoader getExtClassLoader()
throws IOException
{ //可以知道ExtClassLoader類加載路徑為java.ext.dirs
File[] arrayOfFile = getExtDirs();
try
{
(ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction()
{
public Launcher.ExtClassLoader run()
throws IOException
{
int i = this.val$dirs.length;
for (int j = 0; j < i; j++) {
MetaIndex.registerDirectory(this.val$dirs[j]);
}
return new Launcher.ExtClassLoader(this.val$dirs);
}
});
}
catch (PrivilegedActionException localPrivilegedActionException)
{
throw ((IOException)localPrivilegedActionException.getException());
}
}
private static File[] getExtDirs()
{
String str = System.getProperty("java.ext.dirs");
File[] arrayOfFile;
if (str != null)
{
StringTokenizer localStringTokenizer = new StringTokenizer(str, File.pathSeparator);
int i = localStringTokenizer.countTokens();
arrayOfFile = new File[i];
for (int j = 0; j < i; j++) {
arrayOfFile[j] = new File(localStringTokenizer.nextToken());
}
}
else
{
arrayOfFile = new File[0];
}
return arrayOfFile;
}
下面看下AppClassLoader.getAppClassLoader的代碼
public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)
throws IOException
{ //可知AppClassLoader類加載路徑為java.class.path
String str = System.getProperty("java.class.path");
final File[] arrayOfFile = str == null ? new File[0] : Launcher.getClassPath(str);
(ClassLoader)AccessController.doPrivileged(new PrivilegedAction()
{
public Launcher.AppClassLoader run()
{
URL[] arrayOfURL = this.val$s == null ? new URL[0] : Launcher.pathToURLs(arrayOfFile);
return new Launcher.AppClassLoader(arrayOfURL, paramClassLoader);
}
});
}
總結(jié)下Java應(yīng)用啟動(dòng)過(guò)程是首先BootstarpClassloader加載rt.jar包里面的sun.misc.Launcher類录别,而該類內(nèi)部使用BootstarpClassloader加載器構(gòu)建和初始化Java中三種類加載和線程上下文類加載器,然后在根據(jù)不同場(chǎng)景去使用這些類加載器去自己的類查找路徑去加載類邻吞。
三组题、一種特殊的類加載器-ContextClassLoader
閱讀過(guò)tomcat和集團(tuán)中間件源碼的童鞋對(duì)ContextClassLoader應(yīng)該很熟悉了,可以很多地方看到下面的結(jié)構(gòu)的用法:
//獲取當(dāng)前線程上下文類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {//設(shè)置當(dāng)前線程上下文類加載器為targetTccl
Thread.currentThread().setContextClassLoader(targetTccl);
//doSomething
doSomething();
} finally {//設(shè)置當(dāng)前線程上下文加載器為原始加載器
Thread.currentThread().setContextClassLoader(classLoader);
}
doSomething里面則調(diào)用了 Thread.currentThread().getContextClassLoader()獲取了當(dāng)前線程上下文類加載器來(lái)做了某些事情抱冷。那么這其中的奧秘和使用場(chǎng)景是哪里那崔列?我們知道Java默認(rèn)的類加載機(jī)制是委托機(jī)制,但是有時(shí)這種加載順序不能正常工作旺遮,通常發(fā)生在有些JVM核心代碼必須動(dòng)態(tài)加載由應(yīng)用程序開發(fā)人員提供的資源時(shí)赵讯。以JNDI舉例:它的核心內(nèi)容和接口在rt.jar中的引導(dǎo)類中實(shí)現(xiàn)了盈咳,但是這些JNDI實(shí)現(xiàn)類可能加載由獨(dú)立廠商實(shí)現(xiàn)和部署在應(yīng)用程序的classpath中的JNDI提供者。這個(gè)場(chǎng)景要求一個(gè)父類加載器(這個(gè)例子中指加載rt.jar的bootstarp加載器)去加載一個(gè)在它的子類加載器(AppClassLoader)中可見(jiàn)的類边翼。此時(shí)通常的J2SE委托機(jī)制就不能勝任猪贪,解決辦法是讓JNDI核心類使用線程上下文加載器(從2.6節(jié)知道默認(rèn)線程上下文加載器為AppClassLoader)。
具體來(lái)說(shuō)在比如Java中的spi,SPI的全名為Service Provider Interface,spi是面向接口編程讯私,服務(wù)規(guī)則提供者會(huì)在Java核心類理他提供服務(wù)訪問(wèn)接口,而具體實(shí)現(xiàn)則有其他開發(fā)商提供西傀,我們知道Java核心類斤寇,例如rt.jar包是有bootstrap加載,而用戶提供的jar包在由appclassloader加載拥褂。另外我們知道如果一個(gè)類是有A加載器加載娘锁,那么A類依賴或者引用的類也是有相同的加載器加載。那么有bootstarp加載的類怎么加載到本來(lái)應(yīng)該有appclassloadr加載的類那饺鹃,這時(shí)候線程上下文類加載就派上用處了。
在 例如jdbc4也基于spi的機(jī)制來(lái)發(fā)現(xiàn)驅(qū)動(dòng)提供商了,可以通過(guò)META-INF/services/java.sql.Driver文件里指定實(shí)現(xiàn)類的方式來(lái)暴露驅(qū)動(dòng)提供者.JDBC服務(wù)提供商只需要實(shí)現(xiàn)java.sql.dirver就可以了群发。例如mysql驅(qū)動(dòng)
而mysql的驅(qū)動(dòng)如下實(shí)現(xiàn)了java.sql.Driver
public class com.mysql.jdbc.Driver extends com.mysql.jdbc.NonRegisteringDriver implements java.sql.Driver
我們寫個(gè)測(cè)試代碼宰睡,看看是如何工作的:
public class MyContextClassLoad {
public static void testContextClassLoader() {
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while (iterator.hasNext()) {
Driver driver = (Driver) iterator.next();
System.out.println("driver:" + driver.getClass() + ",loader:" + driver.getClass().getClassLoader());
}
System.out.println("current thread contextloader:"+Thread.currentThread().getContextClassLoader());
System.out.println("current loader:" + MyContextClassLoad.class.getClassLoader());
System.out.println("ServiceLoader loader:" + ServiceLoader.class.getClassLoader());
}
public static void main(String []arg){
testContextClassLoader();
}
}
執(zhí)行結(jié)果如下:
driver:class com.mysql.jdbc.Driver,loader:sun.misc.Launcher$AppClassLoader@53d9f80
current thread context loader:sun.misc.Launcher$AppClassLoader@53d9f80
current loader:sun.misc.Launcher$AppClassLoader@53d9f80
ServiceLoader loader:null
從執(zhí)行結(jié)果可以知道ServiceLoader的加載器為Bootstarp,因?yàn)檫@里輸出了null,并且從該類在rt.jar里面也證明了這個(gè)說(shuō)法.而com.mysql.jdbc.Driver則使用AppClassLoader加載茄螃。我們知道如果一個(gè)類中引用了另外一個(gè)類缝驳,那么這被引用的類也應(yīng)該由引用方類加載器來(lái)加載,而現(xiàn)在則是引用方ServiceLoader使用BootStarpClassloader加載归苍,被引用方則使用偽子加載器APPClassLoader來(lái)加載了用狱,是不是很詭異
下面我們來(lái)看下源碼:
public final class ServiceLoader<S> implements Iterable<S> {
public static <S> ServiceLoader<S> load(Class<S> service) {
// 獲取當(dāng)前線程上下文,本例子里面是AppClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = svc;
loader = cl;
reload();
}
public S next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 這里使用AppClassLoader加載mysql實(shí)現(xiàn)的spi類
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service, "Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service, "Provider " + cn + " could not be instantiated", x);
}
throw new Error(); // This cannot happen
}
}
我們?cè)谧隽硗庖粋€(gè)實(shí)驗(yàn):
package myclassloader;
import java.io.FileNotFoundException;
import java.net.URL;
import org.springframework.transaction.annotation.Transactional;
public static void testContextClassLoader() {
//獲取extclassloader
ClassLoader extClassloader = MyContextClassLoad.class.getClassLoader().getParent();
System.out.println("extloader:" +extClassloader);
//設(shè)置當(dāng)前線程上下文加載器為ext
Thread.currentThread().setContextClassLoader(extClassloader);
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while (iterator.hasNext()) {
Driver driver = (Driver) iterator.next();
System.out.println("driver:" + driver.getClass() + ",loader:" + driver.getClass().getClassLoader());
}
System.out.println("current thread context loader:" +Thread.currentThread().getContextClassLoader());
System.out.println("current loader:" + MyContextClassLoad.class.getClassLoader());
System.out.println("ServiceLoader loader:" + ServiceLoader.class.getClassLoader());
}
extloader:sun.misc.Launcher$ExtClassLoader@10b28f30
current thread context loader:sun.misc.Launcher$ExtClassLoader@10b28f30
current loader:sun.misc.Launcher$AppClassLoader@53d9f80
ServiceLoader loader:null
從結(jié)果可以知道沒(méi)有加載到mysql驅(qū)動(dòng)類拼弃,這是因?yàn)閙ysql的jar包是放到了classpath下夏伊,而extclassloader查找路徑為ext目錄所致。
總結(jié)下:當(dāng)父類加載器需要加載子類加載器中的資源時(shí)候可以通過(guò)設(shè)置和獲取線程上下文類加載器來(lái)實(shí)現(xiàn)吻氧,其實(shí)另外還有一種情況就是一個(gè)類加載器要使用不在當(dāng)前類加載器類查找路徑路徑中的情況溺忧,這種情況下可以新建一個(gè)在指定路徑查找類的類加載器,其實(shí)下面第五節(jié)要講的pandora類加載機(jī)制就是這樣盯孙。
四砸狞、Tomcat ClassLoader
4.1 Tomcat classloader的構(gòu)造
首先我們打開tomcat的源碼Bootstrap類的initClassLoaders方法:
private void initClassLoaders() {
try {
// 創(chuàng)建commonLoader,父類為APPClassLoader
commonLoader = createClassLoader("common", this.getClass().getClassLoader());
if( commonLoader == null ) {
commonLoader=this.getClass().getClassLoader();
}
//以commonLoader為父加載器創(chuàng)建catalinaLoader和sharedLoader加載器
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
現(xiàn)在我們已經(jīng)知道這三個(gè)加載器關(guān)系了镀梭,下面看下創(chuàng)建加載器的代碼:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
//獲取catalina.properties中配置刀森,分別為:common.loader,server.loader报账,shared.loader
String value = CatalinaProperties.getProperty(name + ".loader");
//根據(jù)配置知道server.loader研底,shared.loader為空埠偿,所以commonLoader=serverLoader=sharedLoader
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));
}
}
//這里其實(shí)是URLClassLoader,只是urls會(huì)不同
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
這里根據(jù)配置文件catalina.properties里面配置的倉(cāng)庫(kù)地址作為類加載器查找類路徑榜晦,配置如下:
common.loader=${catalina.base}/lib,${catalina.base}/lib/.jar,${catalina.home}/lib,${catalina.home}/lib/.jar
server.loader=
shared.loader=
根據(jù)上面代碼可知commonLoader,serverLoader,sharedLoader是同一個(gè)classloader冠蒋。
然后繼續(xù)看bootstarp的init方法:
public void init()
throws Exception
{
.....
//上面分析的
initClassLoaders();
//設(shè)置當(dāng)前線程上下文加載器為catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
//使用catalinaLoader加載Catalina類
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// 設(shè)置Catalina的父加載器為sharedLoader.
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;
}
這里設(shè)置了Catalina里面的parentClassLoader=sharedLoader,下面我們看web應(yīng)用的classloader如何被創(chuàng)建。
研究過(guò)tomcat的童鞋應(yīng)該都知道tomcat的容器構(gòu)造:
其中Engine是最大的容器默認(rèn)為StandardEngine乾胶,其中可以有若干個(gè)host默認(rèn)為StandardHost抖剿,host的父容器為Engine,每個(gè)host容器里面有若干context容器默認(rèn)為StandardContext识窿,context容器的父容器為host.
從catalina.java的createStartDigester函數(shù):
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
知道StandardEngine中的parentClassLoader被設(shè)置為了sharedLoader斩郎。
然后我們看下StandardContext中的startInternal方法:
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
//設(shè)置到類對(duì)象
setLoader(webappLoader);
}
其中g(shù)etParentClassLoader方法用來(lái)獲取父類加載器這里為sharedLoader,具體原因看如下代碼:
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null)
return (parentClassLoader);
if (getPrivileged()) {
return this.getClass().getClassLoader();
} else if (parent != null) {
//這里StandardContext的parentClassLoader為空喻频,則會(huì)調(diào)用StandardHost的parentClassLoader方法缩宜,也為空,則會(huì)調(diào)用StandardEngine的parentClassLoader方法甥温,而它返回的正是sharedLoader锻煌。
return (parent.getParentClassLoader());
}
return (ClassLoader.getSystemClassLoader());
}
上面創(chuàng)建了WebappLoader并且設(shè)置到了StandardContext的loader屬性,下面調(diào)用loader的start方法啟動(dòng)web加載器的創(chuàng)建:
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
進(jìn)入WebappLoader的startInternal方法:
protected void startInternal() throws LifecycleException {
..........
// Construct a class loader based on our current repositories list
try {
if (classLoader == null) {//這里創(chuàng)建web應(yīng)用類加載器
classLoader = createClassLoader();
}
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);
if (container instanceof StandardContext) {
classLoader.setAntiJARLocking(
((StandardContext) container).getAntiJARLocking());
classLoader.setClearReferencesRmiTargets(
((StandardContext) container).getClearReferencesRmiTargets());
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]);
}
} catch (Throwable t) {
.......
}
setState(LifecycleState.STARTING);
}
createClassLoader的代碼如下:
/**
* Create associated classLoader.
*/
private String loaderClass =
"org.apache.catalina.loader.WebappClassLoader";
private WebappClassLoaderBase createClassLoader()
throws Exception {
//創(chuàng)建WebappClassLoader類
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoaderBase classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = container.getParentClassLoader();
}
//設(shè)置WebappClassLoader的父加載器為sharedLoader
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args);
return classLoader;
}
至此創(chuàng)建了應(yīng)用的類加載器姻蚓,由于每個(gè)standardcontext對(duì)應(yīng)一個(gè)web應(yīng)用宋梧,所以不同的應(yīng)用都有不同的
WebappClassLoader,共同點(diǎn)是他們的父加載器都是sharedLoader狰挡。下面列下tomcat類加載器關(guān)系圖:
4.2 Tomcat classloader
tomcat原生加載器:
原生tomcat加載器里面查找乃秀,下面說(shuō)說(shuō)tomcat自身的web類加載器邏輯:
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLockInternal(name)) {
........
Class<?> clazz = null;
........
// (0) 首先檢查webloader緩存中是否已經(jīng)加載該類
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.1) 看jvm緩存是否已經(jīng)加載該類
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.2) 嘗試在extClassloader查找該類
try {
clazz = j2seClassLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
.....
boolean delegateLoad = delegate || filter(name);
// (1) 如果在context.xml配置了代理,則委托給父類加載器sharedclassloader來(lái)加載
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
}
}
// (2) 在web應(yīng)用的WEB-INF/lib下查找
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
}
// (3) 如果沒(méi)有context.xml配置代理圆兵,則委托給父類加載器sharedclassloader來(lái)加載跺讯,和(1)只有一個(gè)存在
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);
}
其中(0.2) 之前一直以為是appclassloader來(lái)加載,但是源碼后發(fā)現(xiàn)竟然是extclassloader.
public WebappClassLoaderBase(ClassLoader parent) {
super(new URL[0], parent);
ClassLoader p = getParent();
if (p == null) {
p = getSystemClassLoader();
}
this.parent = p;
ClassLoader j = String.class.getClassLoader();
if (j == null) {
j = getSystemClassLoader();
while (j.getParent() != null) {
j = j.getParent();
}
}
this.j2seClassLoader = j;
securityManager = System.getSecurityManager();
if (securityManager != null) {
refreshPolicy();
}
}
其實(shí)在tomcat里面使用ContextClassloader的地方也隨處可以殉农,還比如StandardContext中的startInternal方法刀脏,我們知道StandardContext是有catalinaclassloader加載的,而startInternal里面則可以創(chuàng)建我們?cè)趙eb.xml中配置的listener和filter(這些明顯應(yīng)該由webappclassloader加載)
//綁定當(dāng)前線程上下文加載器為webappclassloader
oldCCL = bindThread();
try {
// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
ok = false;
}
}
// Configure and call application filters
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
總結(jié)下超凳,默認(rèn)情況下tomcat中commonloader,sharedloader,catalinaloader是同一個(gè)加載器愈污,其類查找路徑都是同一個(gè)地方。其實(shí)catalinaloader主要工作應(yīng)該是加載tomcat本身啟動(dòng)所需要的類轮傍,而sharedloader是webappclassloader的父類暂雹,所以應(yīng)該是加載一些所有webap共享的類,而commonlaoder作為sharedloader,catalinaloader的父類创夜,自然設(shè)計(jì)目的是為了加載二者共享的類杭跪。所以如果能恰當(dāng)?shù)氖褂胻omcat中設(shè)計(jì)的這種策略,修改catalina.properites中三種加載器類加載路徑,就會(huì)真正達(dá)到這種設(shè)計(jì)效果涧尿。
歡迎關(guān)注微信公眾號(hào):‘技術(shù)原始積累’ 獲取更多技術(shù)干貨__