Tomcat 9 源码分析(2)— 类加载体系
Java虚拟机主要类加载器
- Bootstrap Loader(启动类加载器):加载lib目录下或者
System.getProperty("sun.boot.class.path")、或者-XBootclasspath所指定的路径或jar
-
Extended Loader(扩展类加载器):加载lib\ext目录下或者
System.getProperty("java.ext.dirs")所指定的路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproject\classes HelloWorld -
System Loader(系统类加载器):加载
System.getProperty("java.class.path")所指定的路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如:java -cp ./testsoft/classes HelloWorld
System Loader 也可以称为应用类加载器 Application Loader
Tomcat的类加载体系
除了JVM规范提到的以上三种类加载器之外,Tomcat还实现了自身的类加载体系。各个类加载器之间不是继承关系,而是一种委派关系
- ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现
- commonLoader:Tomcat最基本的类加载器,加载路径中class可以被Tomcat容器本身以及各个Webapp访问
- catalinaLoader(serverLoader):Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见
- sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有的Webapp课件,但是对Tomcat容器不可见
- WebappClassLoader:各个Webapp私有的累加载器,加载路径中的class只对当前Webapp可见
源码分析
commonLoader、catalinaLoader和SharedLoader是在Tomcat容器初始化的过程刚刚开始,即调用Bootstrap.init()时创建的。catalinaLoader会被设置为Tomcat主线程的线程上下文类加载器,并且使用catalinaLoader加载Tomcat容器自身class
org.apache.catalina.startup.Bootstrap.init()
public void init() throws Exception {
// 初始化catalina.properties中的commonLoader,serverLoader,sharedLoader
// 如果catalina.properties中的server.loader和sharedLoader为空则以common.Loader替代
// 查找各个loader中的系统变量,其中包含了以","分隔的文件路径(URL、JAR、目录等),称之为repository
// 调用ClassLoaderFactory.createClassLoader()产生classLoader
initClassLoaders();
// 把生成的catalinaLoader设置为当前线程上下文的classloader
Thread.currentThread().setContextClassLoader(catalinaLoader);
// 线程安全加载class
classSecurityClassLoad.securityClassLoad(catalinaLoader);
// 省略后面的code
}
在Bootstrap.main()内对ClassLoader的处理如下:
- 初始化commonLoader、catalinaLoader、sharedLoader
- 将catalinaLoader设置为Tomcat主线程的线程上下文加载器
- 线程安全加载class
初始化类加载器
org.apache.catalina.startup.Bootstrap.initClassLoaders()
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);
}
}
initClassLoader调用createClassLoader()创建commonLoader、catalinaLoader和sharedLoader
org.apache.catalina.startup.Bootstarp.createClassLoader()
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<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// 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));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
在最后调用ClassLoaderFactory.createClassLoader()创建classLoader
org.apache.catalina.startup.ClassLoaderFactory.createClassLoader()
// 省略部分code
return AccessController.doPrivileged(
(PrivilegedAction<URLClassLoader>) () -> {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
});
其内代码的步骤为:
- 定位资源路径与资源类型
- 使用ClassLoaderFactory创建类加载器UrlClassLoader(旧版本Tomcat为创建
org.apache.catalina.loader.StandardClassLoader新版本中已被抛弃)
注:Tomcat默认会指定commonLoader(通过catalina.properties的common.loader属性)
tomcat/conf/catalina.properties
//common.loader:tomcat自身和各web服务都会用到的类
//server.loader:tomcat自身会用到的类 如果为空则由commonLoader替代
//shared.loader:各web服务会用到的类 如果为空则由commonLoader替代
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
安全加载class
Bootstrap.init()内完成了初始化类加载器后便是安全加载class
org.apache.catalina.security.SecurityClassLoad.securityClassLoad()
public static void securityClassLoad(ClassLoader loader) throws Exception {
securityClassLoad(loader, true);
}
static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager) throws Exception {
if (requireSecurityManager && System.getSecurityManager() == null) {
return;
}
// Tomcat核心class - org.apache.catalina.core
loadCorePackage(loader);
// Tomcat对底层socket连接数据的封装框架 - org.apache.coyote
loadCoyotePackage(loader);
// Tomcat提供的根据名称寻找类以及一个日志处理 - org.apache.catalina.loader.WebappClassLoaderBase内的两个内部类
loadLoaderPackage(loader);
// Tomcat的Realm实现,对多次验证失败的用户提供锁定功能 - org.apache.catalina.realm
loadRealmPackage(loader);
// Tomcat的默认Servlet - org.apache.catalina.servlets.DefaultServlet
loadServletsPackage(loader);
// Tomcat有关session的class - org.apache.catalina.session
loadSessionPackage(loader);
// Tomcat工具类的class - org.apache.catalina.util
loadUtilPackage(loader);
// 处理Cookie - javax.servlet.http.Cookie
loadJavaxPackage(loader);
// Tomcat Connector(连接器)组件 - org.apache.catalina.connector
loadConnectorPackage(loader);
// Tomcat其他工具类的class - org.apache.tomcat.util
loadTomcatPackage(loader);
}
securityClassLoad()内执行的方法作用如以上的注释
securityClassLoad主要负责加载Tomcat容器所需的class,具体不详情描述自行查看
WebappClassLoader的实现
在StandardContext的初始化方法中,最后会调用WebappLoader的start方法,start中又调用了startInternal方法
org.apache.catalina.core.StandardContext.startInternal()```
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// 省略前面其余代码
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader();
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
// 省略中间其余代码
// Start our subordinate components, if any
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
// 省略后面其余代码
}
org.apache.catalina.loader.WebappLoader.startInternal()
/**
* Start associated {@link ClassLoader} and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected void startInternal() throws LifecycleException {
if (log.isDebugEnabled())
log.debug(sm.getString("webappLoader.starting"));
if (context.getResources() == null) {
log.info(sm.getString("webappLoader.noResources", context));
setState(LifecycleState.STARTING);
return;
}
// Construct a class loader based on our current repositories list
try {
classLoader = createClassLoader();
classLoader.setResources(context.getResources());
classLoader.setDelegate(this.delegate);
// Configure our repositories
setClassPath();
setPermissions();
classLoader.start();
String contextName = context.getName();
if (!contextName.startsWith("/")) {
contextName = "/" + contextName;
}
ObjectName cloname = new ObjectName(context.getDomain() + ":type=" +
classLoader.getClass().getSimpleName() + ",host=" +
context.getParent().getName() + ",context=" + contextName);
Registry.getRegistry(null, null)
.registerComponent(classLoader, cloname, null);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
throw new LifecycleException(sm.getString("webappLoader.startError"), t);
}
setState(LifecycleState.STARTING);
}
WebappLoader.startInternal()调用了createClassLoader()创建类加载器
org.apache.catalina.loader.WebappLoader.createClassLoader()
/**
* Create associated classLoader.
*/
private WebappClassLoaderBase createClassLoader()
throws Exception {
if (classLoader != null) {
return classLoader;
}
if (parentClassLoader == null) {
parentClassLoader = context.getParentClassLoader();
} else {
context.setParentClassLoader(parentClassLoader);
}
if (ParallelWebappClassLoader.class.getName().equals(loaderClass)) {
return new ParallelWebappClassLoader(parentClassLoader);
}
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoaderBase classLoader = null;
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args);
return classLoader;
}
这里的parentClassLoader实际就是sharedLoader,至此整个Tomcat的类加载体系构建完毕
Tomcat重写了loadClass使类加载得以隔离
org.apache.catalina.loader.WebappClassLoaderBase.loadClass()
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// (0.1) Check our previously loaded class cache
clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" 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 Java SE classes. This implements
// SRV.10.7.2
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
// The getResource() trick won't work for this class. We have to
// try loading it directly and accept that we might get a
// ClassNotFoundException.
tryLoadingFromJavaseLoader = true;
}
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.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 = sm.getString("webappClassLoader.restrictedPackage", name);
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested
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) Search local repositories
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
}
// (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);
}
WebappClassLoaderBase加载class步骤:
- 从之前加载的class的缓存中查找
- 委托sun.misc.Launcher$AppClassLoader加载class
- 安全检查通过后,委托父类加载器SharedClassLoader加载Class
- WebappClassLoader自己加载class
总结
WebappClassLoaderBase的findClass实现主要调用findClassInternal方法来加载webapp自身路径下的class
浙公网安备 33010602011771号