开篇
这是一篇尝试讲解清楚Tomcat的类加载器的文章,估摸着能讲清楚六成左右,待后续再理理思路。
文末有彩蛋,可以直接翻到文章末尾。
Tomcat 类加载器概览
说明:
-
- BootstrapClassLoader : 系统类加载器,加载%JAVA_HOME%/lib目录下的jar
-
- ExtClassLoader : 扩展类加载器,加载%JAVA_HOME%/ext/lib目录下的jar
-
- AppClassLoader : 普通类加载器,加载CLASSPATH指定目录下的jar
-
- commonLoader : Tomcat 通用类加载器, 加载的资源可被 Tomcat 和 所有的 Web 应用程序共同获取
-
- catalinaLoader : Tomcat 类加载器, 加载的资源只能被 Tomcat 获取(但 所有 WebappClassLoader 不能获取到 catalinaLoader 加载的类)
-
- sharedLoader : Tomcat 各个Context的父加载器, 这个类是所有 WebappClassLoader 的父类, sharedLoader 所加载的类将被所有的 WebappClassLoader 共享获取
-
- 这个版本 (Tomcat 8.x.x) 中, 默认情况下 commonLoader = catalinaLoader = sharedLoader
-
- (PS: 为什么这样设计, 主要这样这样设计 ClassLoader 的层级后, WebAppClassLoader 就能直接访问 tomcat 的公共资源, 若需要tomcat 有些资源不让 WebappClassLoader 加载, 则直接在 ${catalina.base}/conf/catalina.properties 中的 server.loader 配置一下 加载路径就可以了)
说明:
-
- Common、Catalina、Shared类加载器继承自URLCLassLoader。
-
- WebappClassLoader继承WebappClassLoaderBase继承URLCLassLoader。
- 3、JVM自带的ExtClassLoader也是URLClassLoader的子类。
- 4、JVM自带的AppClassLoader也是URLClassLoader的子类。
Tomcat 各类ClassLoader初始化
Common&Catalina&Shared ClassLoader
-
- Bootstrap.init()初始化Tomcat的类加载器。
-
- Bootstrap的initClassLoaders()初始化Common、Catalina、Shared的类加载器。
-
- Common ClassLoader的父加载器是AppClassLoader。
-
- Catalina ClassLoader = Common ClassLoader。
-
- Shared ClassLoader = Common ClassLoader。
public final class Bootstrap {
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
// 初始化Bootstrap
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
}
}
public void init() throws Exception {
// 初始化Tomcat的类加载器
initClassLoaders();
}
private void initClassLoaders() {
try {
// 创建commonLoader并且未指定父节点,默认为
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并且指定parent为commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
// 创建SharedLoader并且指定parent为commonLoader
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
}
ClassLoader创建过程
-
- CatalinaProperties的getProperty方法加载配置conf/Catalina/catalina.properties。
-
- catalina.properties的配置文件内容中:
common.loader="${catalina.base}/lib","\${catalina.base}/lib/.jar","${catalina.home}/lib","\${catalina.home}/lib/.jar"。
- catalina.properties的配置文件内容中:
-
- catalina.properties的配置文件内容中:server.loader=,返回common classLoader。
-
- catalina.properties的配置文件内容中:shared.loader=,返回common classLoader。
public final class Bootstrap {
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
// common.loader配置jar路径,负责加载${catalina.base}/lib和{catalina.home}/lib
// server.loader和shared.loader配置路径为空,所以返回parent即common classloader
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
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));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
}
catalina.properties配置解析
-
- 解析tomcat目录下的conf/Catalina/catalina.properties配置文件。
-
- server.loader和shared.loader的值为空
-
- common.loader="${catalina.base}/lib","\${catalina.base}/lib/.jar","${catalina.home}/lib","\${catalina.home}/lib/.jar"
public class CatalinaProperties {
private static Properties properties = null;
static {
loadProperties();
}
public static String getProperty(String name) {
return properties.getProperty(name);
}
private static void loadProperties() {
InputStream is = null;
if (is == null) {
try {
// conf/Catalina/catalina.properties
// common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
// server.loader=
// shared.loader=
File home = new File(Bootstrap.getCatalinaBase());
File conf = new File(home, "conf");
File propsFile = new File(conf, "catalina.properties");
is = new FileInputStream(propsFile);
} catch (Throwable t) {
}
}
if (is != null) {
try {
properties = new Properties();
properties.load(is);
} catch (Throwable t) {
} finally {
try {
is.close();
} catch (IOException ioe) {
}
}
}
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String name = (String) enumeration.nextElement();
String value = properties.getProperty(name);
if (value != null) {
System.setProperty(name, value);
}
}
}
}
ClassLoaderFactory的ClassLoader工厂
-
- 加载repositories对应的jar目录
-
- new URLClassLoader(array)创建没传parent的ClassLoader,这种情况下父加载器是AppClassLoader。
-
- new URLClassLoader(array, parent)创建以parent作为父加载器的ClassLoader。
public final class ClassLoaderFactory {
public static ClassLoader createClassLoader(List<Repository> repositories,
final ClassLoader parent)
throws Exception {
Set<URL> set = new LinkedHashSet<>();
if (repositories != null) {
for (Repository repository : repositories) {
if (repository.getType() == RepositoryType.URL) {
URL url = buildClassLoaderUrl(repository.getLocation());
set.add(url);
} else if (repository.getType() == RepositoryType.DIR) {
File directory = new File(repository.getLocation());
URL url = buildClassLoaderUrl(directory);
set.add(url);
} else if (repository.getType() == RepositoryType.JAR) {
File file=new File(repository.getLocation());
file = file.getCanonicalFile();
URL url = buildClassLoaderUrl(file);
set.add(url);
} else if (repository.getType() == RepositoryType.GLOB) {
File directory=new File(repository.getLocation());
directory = directory.getCanonicalFile();
String filenames[] = directory.list();
if (filenames == null) {
continue;
}
for (int j = 0; j < filenames.length; j++) {
String filename = filenames[j].toLowerCase(Locale.ENGLISH);
File file = new File(directory, filenames[j]);
file = file.getCanonicalFile();
URL url = buildClassLoaderUrl(file);
set.add(url);
}
}
}
}
final URL[] array = set.toArray(new URL[set.size()]);
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}
}
URLClassLoader的实现
说明:
-
- URLClassLoder是JVM当中的实现方式。
-
- URLClassLoder继承SecureClassLoader。
-
- SecureClassLoader继承ClassLoader。
public class URLClassLoader extends SecureClassLoader implements Closeable {
public URLClassLoader(URL[] urls) {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
ucp = new URLClassPath(urls, acc);
}
public URLClassLoader(URL[] urls, ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
ucp = new URLClassPath(urls, acc);
}
}
public class SecureClassLoader extends ClassLoader {
protected SecureClassLoader() {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
initialized = true;
}
}
抽象类ClassLoader
-
- ClassLoader()方法使用getSystemClassLoader作为parent。
-
- getSystemClassLoader()返回sun.misc.Launcher.getLauncher().getClassLoader()。
-
- sun.misc.Launcher.getLauncher().getClassLoader()是。AppClassLoader对象
public abstract class ClassLoader {
private final ClassLoader parent;
private static ClassLoader scl;
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
}
}
sclSet = true;
}
}
}
Launcher
-
- Launcher.AppClassLoader.getAppClassLoader(var1)返回AppClassLoader对象。
public class Launcher {
private static Launcher launcher = new Launcher();
private ClassLoader loader;
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
}
}
}
Webapp Classloader
整个Class loader的传递过程如下,证明Webapp classLoader的parent节点。
StandardEngine通过xml解析获得了Catalina的class loader。
StandardHost通过xml解析获得了StandardEngine的class loader。
StandardContext通过getParentClassLoader获得了StandardHost的class loader。
设置Bootstrap的common、catalina、shared等Loader
Catalina的parent设置
-
- Bootstrap通过反射调用Catalina的setParentClassLoader设置Catalina的parentClassLoader为sharedLoader。
public final class Bootstrap {
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;
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);
}
}
public void init() throws Exception {
initClassLoaders();
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
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;
}
}
StandardEingine的parent设置
-
- StandardEingine的parentClassLoder设置为Catalina当中的parentClassLoader。
-
- Catalina当中的parentClassLoader是sharedLoader。
-
- StandardEingine通过如下方式注入parentClassLoader: digester.addRule("Server/Service/Engine,
- SetParentClassLoaderRule(parentClassLoader));
public class Catalina {
protected ClassLoader parentClassLoader =
Catalina.class.getClassLoader();
public void setParentClassLoader(ClassLoader parentClassLoader) {
this.parentClassLoader = parentClassLoader;
}
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null) {
return (parentClassLoader);
}
return ClassLoader.getSystemClassLoader();
}
// Engine的解析规则
protected Digester createStartDigester() {
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
}
StandardHost的parent设置
-
- StandardHost通过StandardEngine的getParentClassLoader获取ClassLoader。
-
- StandardHost的setParentClassLoader注入StandardEngine的parentClassLoader。
-
- StandardHost的parentClassLoader为sharedLoader。
public class HostRuleSet extends RuleSetBase {
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
// Host的XML的解析规则
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
}
}
public class CopyParentClassLoaderRule extends Rule {
public CopyParentClassLoaderRule() {
}
@Override
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
if (digester.getLogger().isDebugEnabled())
digester.getLogger().debug("Copying parent class loader");
Container child = (Container) digester.peek(0);
Object parent = digester.peek(1);
Method method =
parent.getClass().getMethod("getParentClassLoader", new Class[0]);
ClassLoader classLoader =
(ClassLoader) method.invoke(parent, new Object[0]);
child.setParentClassLoader(classLoader);
}
}
StandardContext的parent设置
-
- StandardContext通过parent.getParentClassLoader()返回parentClassLoader。
-
- StandardContext的parent是StandardHost对象。
-
- StandardHost.getParentClassLoader()返回StandardHost的sharedLoader。
public class StandardContext extends ContainerBase
implements Context, NotificationEmitter {
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null)
return (parentClassLoader);
if (getPrivileged()) {
return this.getClass().getClassLoader();
} else if (parent != null) {
// parent是StandardHost
return (parent.getParentClassLoader());
}
return (ClassLoader.getSystemClassLoader());
}
}
类加载过程双亲委派
WebappClassLoader
-
- WebappClassLoader继承自WebappClassLoaderBase。
-
- 核心实现在于WebappClassLoaderBase类。
public class WebappClassLoader extends WebappClassLoaderBase {
public WebappClassLoader() {
super();
}
public WebappClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public WebappClassLoader copyWithoutTransformers() {
WebappClassLoader result = new WebappClassLoader(getParent());
super.copyStateWithoutTransformers(result);
try {
result.start();
} catch (LifecycleException e) {
throw new IllegalStateException(e);
}
return result;
}
@Override
protected Object getClassLoadingLock(String className) {
return this;
}
}
WebappClassLoaderBase
-
- 调用 findLocaledClass0 从 resourceEntries 中判断 class 是否已经加载 OK。
-
- 调用 findLoadedClass(内部调用一个 native 方法) 直接查看对应的 WebappClassLoader 是否已经加载过。
-
- 调用 binaryNameToPath 判断是否 当前 class 是属于 J2SE 范围中的, 若是的则直接通过 ExtClassLoader, BootstrapClassLoader 进行加载 (这里是双亲委派)。
-
- 在设置 JVM 权限校验的情况下, 调用 securityManager 来进行权限的校验(当前类是否有权限加载这个类, 默认的权限配置文件是 ${catalina.base}/conf/catalina.policy)。
-
- 判断是否设置了双亲委派机制 或 当前 WebappClassLoader 是否能加载这个 class (通过 filter(name) 来决定), 将最终的值赋值给 delegateLoad。
-
- 根据上一步中的 delegateLoad 来决定是否用 WebappClassloader.parent(也就是 sharedClassLoader) 来进行加载, 若加载成功, 则直接返回。
-
- 上一步若未加载成功, 则调用 WebappClassloader.findClass(name) 来进行加载。
-
- 若上一还是没有加载成功, 则通过 parent 调用 Class.forName 来进行加载。
-
- 若还没加载成功的话, 那就直接抛异常。
public abstract class WebappClassLoaderBase extends URLClassLoader
implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
public Class<?> loadClass(String name) throws ClassNotFoundException {
return (loadClass(name, false));
}
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
//首先调用findLoaderClass0() 方法检查WebappClassLoader中是否加载过此类
// WebappClassLoader 加载过的类都存放在 resourceEntries 缓存中。
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 调用 findLoadedClass(内部调用一个 native 方法)
// 直接查看对应的 WebappClassLoader 是否已经加载过
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 调用 binaryNameToPath 判断是否 当前 class 是属于 J2SE 范围中的,
// 若是的则直接通过 ExtClassLoader, BootstrapClassLoader 进行加载
// (这里是双亲委派)
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
} catch (Throwable t) {
tryLoadingFromJavaseLoader = true;
}
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// 判断是否需要委托给父类加载器进行加载,
// delegate属性默认为false,那么delegatedLoad的值就取决于filter的返回值了
// filter中是优先加载tomcat的lib下的class文件
// filter方法中根据包名来判断是否需要进行委托加载,
// 默认情况下会返回false.因此delegatedLoad为false
boolean delegateLoad = delegate || filter(name, true);
// 因为delegatedLoad为false,那么此时不会委托父加载器去加载,
// 这里其实是没有遵循parent-first的加载机制。
if (delegateLoad) {
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
}
}
// 调用findClass方法在webapp级别进行加载
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) {
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);
}
protected Class<?> findLoadedClass0(String name) {
String path = binaryNameToPath(name, true);
ResourceEntry entry = resourceEntries.get(path);
if (entry != null) {
return entry.loadedClass;
}
return null;
}
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
}
参考文章
违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制
Tomcat 源码分析 WebappClassLoader 分析 (基于8.0.5)