java 类加载器的功能是将 class 加载入内存, tomcat的的应用程序加载过程使 tomcat拥有了在同一个 jvm 中加载管理多个应用的功能.
在介绍 tomcat应用程序加载过程前,我们先简单了解下 java 类加载机制.在Class 类中,我们可以看到Class#getClassLoader
方法,通过这个方法我们可以获取到相应类对应的 ClassLoader 信息.
我们在应用程序中去输出不同类的 ClassLoader,比如
public class Test {
public static void main(String[] args) {
System.out.println(Test.class.getClassLoader());
System.out.println(Integer.class.getClassLoader());
}
}
输出的结果是
sun.misc.Launcher$AppClassLoader@18b4aac2
null
即我们自定义的类的 ClassLoader 为Launcher
中的内部类AppClassLoader
,Integer
的 ClassLoader
显示为 null
, 实际是它也是有ClassLoader
的,所有classLoader
为 null
的 class 的classLoader 都是bootstrap class loader
,其中 java 应用程序中是不可见的. bootstrap class loade
是最底层的 ClassLoader
, 它用于加载 jvm
内部的 class
. 在bootstrap classloader
之下是Launcher$ExtClassLoader
, ExtClassLoader用于加载$JAVA_HOME/jre/lib/ext/下的 class; 在 ClassLoader 中,我们可以看到存在 parent 属性, 这里就得提到 ClassLoader 的委派模型,默认情况下,ClassLoader 去加载类时,会先委派给 parent 加载,若 parent 无法加载,再由自身加载,我们可以简单看下ClassLoader#loadClass(String name, boolean resolve)
方法(不相关的代码未展示)
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
//判断 parent 是否为 null, null 即 bootstrap class loader
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
//如parent 未加载相应的 class,则自身加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
return c;
}
在 Launcher$ExtClassLoader
之下是Launcher$AppClassLoader
,也就是我们上面 Test.class 的 ClassLoader,Launcher$AppClassLoader
的 parent 为Launcher$ExtClassLoader
.
默认情况下,我们自已经编写的 Class的 ClassLoader 都是Launcher$AppClassLoader
, 但tomcat 下的 webapps 目录下,是可以拥有多个应用的,如果所有类都使用同一个 ClassLoader 加载,无疑会造成应用程序的混乱. 比如两个应用分别使用 springboot2.x和 springboot1.x, 同时存在两个版本的版本冲突问题相应大家都有遇到.
tomcat 加载类的过程,可从org.apache.catalina.startup.Bootstrap#main
入口,在initClassLoaders()
方法中,可以看到 tomcat 初始化了三个 ClassLoader. common,server(catalina)与 shared.
private void initClassLoaders() {
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);
}
其中 common 是 shared 与 catalina的 parent,每个 ClassLoader 对应的类范围在$TOMCAT_HOME/conf/catalina.properties
配置中可见,
common.loader="${catalina.base}/lib".....
server.loader=
shared.loader=
顾名思义,common 为公共基础ClassLoader,shared 为多个应用的共享的 ClassLoader,Server 为服务器的 ClassLoader.
在 tomcat 中,继续往下看,我们可以看到在init
方法中
public void init() throws Exception {
Thread.currentThread().setContextClassLoader(catalinaLoader);
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);
}
最终的结果是线程上下文的 ClassLoader 为catalinaLoader(serverLoader)
,Catalina
的 parentClassLoader
为 sharedClassLoader
.(Catalina不是 ClassLoader,只是存储了 parentClassLoader 信息,下面的StandardContext也是)
然后再看StandardContext#startInternal
protected synchronized void startInternal() throws LifecycleException {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
这里将 Catalina 中的 parentLoader 放入了 WebAppLoader 中,
Webapp中定义了属性
private String loaderClass = ParallelWebappClassLoader.class.getName();
ParallelWebappClassLoader也就是我们应用服务的 ClassLoader,具体可见方法WebAppLoader#createClassLoader()
private WebappClassLoaderBase createClassLoader()
throws Exception {
Class> clazz = Class.forName(loaderClass);
WebappClassLoaderBase classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = context.getParentClassLoader();
}
Class>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args);
return classLoader;
}
创建ParallelWebappClassLoader
后,startInternal()
中执行了
classLoader.setResources(context.getResources());
将 classLoader 与应用中class 绑定.
Context 与 WebAppLoader 也进行关联.
这样,不同的 应用有了不同的 classLoader,但他们的 parent 都是 sharedClassLoader,也就是说他们可以共享sharedClassLoader 以上的所有类.
而后请求过来时,首先经过的不是ParallelWebappClassLoader,因为没有 url 是没法判断是哪个应用的,这时是 catalinaLoader(serverLoader)处理请求,根据请求 url 寻找到对应的 context,再切换至对应应用的ParallelWebappClassLoader,对用户的请求进行处理.
StandardHostValue#invoke
方法可见
public final void invoke(Request request, Response response)
throws IOException, ServletException {
Context context = request.getContext();
context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
}
切换 classLoder 的方法可见
StandardContext#bind
public ClassLoader bind(boolean usePrivilegedAction, ClassLoader originalClassLoader) {
Loader loader = getLoader();
ClassLoader webApplicationClassLoader = null;
if (loader != null) {
webApplicationClassLoader = loader.getClassLoader();
}
Thread.currentThread().setContextClassLoader(webApplicationClassLoader);
}
以上即 tomcatClassLoader 加载与切换的简略过程.