上一部分讲到关于tomcat中的类加载器部分,个人认为在tomcat8以后tomcat并未通过,commonLoader,catalinaLoader,sharedLoader三者来打破双亲委派的模型,因为在tomcat8中并未对catalinaLoader和sharedLoader两者对应的文件进行配置,所以在默认的情况下通过源码我们可以得知三个类加载器都是一样的,都是创建了一个URLClassLoader这个类加载器是引用类加载器的父类,他的loadClass方法并未重写,还是会去找其父类,这三个类加载器的父类都为应用类加载器,所以此处在tomcat8以后的默认配置中并未打破双亲委派模型,而tomcat真正打破双亲委派模型的地方是WebAppClassLoader和JspClassLoader也就是每个应用的类加载器和jsp的类加载器,本节主要讲下WebAppClassLoader的运行原理,后续会讲述JspClassLoader的具体实现。
先上一段创建WebAppClassLoader的代码:
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
这段代码是在org.apache.catalina.core.StandardContext中的startInternal方法中实现的在这个位置进行了每个应用的类加载器的初始化,此时的父类加载器为CommonClassLoader,也就是在BootStrap中创建的URLClassLoader,此时便是一个Context一个类加载器。
这个WebappLoader类继承了WebappClassLoaderBase然后此时我们来看下关于WebappClassLoaderBase的loadClass方法的实现流程(只保留了重点代码):
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
// (0.2) ClassLoader装载系统classpath下面的类, 阻止webapp覆盖了J2SE的类
String resourceName = binaryNameToPath(name, false);
//此时的javaseClassLoader是扩展类加载器 是把扩展类加载器赋值给了javaseClassLoader
//是在构造方法中进行的赋值详见222 和 257行
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
URL url;
//这里就是8以后为了避免产生classNotfound异常产生高昂的代价而用getResource去获取一下
//实际上在启动时候加载了此类用来解析xml
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;
}
//如果可以用getResource得到
//如果能用扩展类加载器的getResource得到就证明可以被扩展类加载器加载到接下来安排扩展类加载器加载
if (tryLoadingFromJavaseLoader) {
try {
//使用扩展类加载器进行加载
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
//过滤classname javax 和 org包下的除了 servlet.jsp.jstl 和 org.apache.tomcat.jdbc.其余返回true
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested
//如果是true就是用父类加载器进行加载
if (delegateLoad) {
if (log.isDebugEnabled()) {
log.debug(" Delegating to parent classloader1 " + parent);
}
System.out.println("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) Search local repositories
if (log.isDebugEnabled()) {
log.debug(" Searching local repositories");
}
//如果是false就使用本地的仓库
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) Delegate to parent unconditionally
//到这里还是没有加载上再次尝试使用父类加载器进行加载
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);
}
通过观察代码会发现此时类的加载过程已经不是完全交给父类加载器了,而是只有部分的类交由应用类加载器和扩展类加载器加载,classpath下的类就是由自己去加载的,所以说tomcat是通过覆写loadClass方法实现的打破双亲委派模型。以上便是WebAppClassLoader的运行过程。