浅析 Tomcat类加载过程

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,IntegerClassLoader显示为 null, 实际是它也是有ClassLoader 的,所有classLoadernull 的 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),CatalinaparentClassLoadersharedClassLoader.(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 加载与切换的简略过程.

你可能感兴趣的:(java,框架)