Tomcat源码五部曲(四)、类加载器

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

在Tomcat中提供了三个基础的类加载器和Web应用类加载器,如下图,绿色是JDK本身类加载器,蓝色则为Tomcat定义的类加载器,这三个类加载器指向的路径和包列表可以由catalina.properties配置,此文件位于Tomcat_Home/conf下。Tomcat源码五部曲(四)、类加载器

Common

他以System为父类加载器,是用于加载Tomcat应用服务器顶层的公用类加载器,其路径为common.loader,默认指向catalina.home/lib下的包。他加载的类对于Web应用均可见。

Catalina

以Common为父加载器,用于加载Tomcat应用服务器的类加载器,其路径为server.loader,默认为空。他加载的类对于Web应用不可见。

Shared

以Common为父加载器,是所有Web应用的父加载器,其路径为shared.loader,默认也为空。

Web应用

以Shared为父加载器,加载/WEB-INF/classes目录下的未压缩的Class和资源文件以及/WEB-INF/lib目录下的Jar包,这个类加载器只对当前Web应用可见,对其他Web应用不可见。

自定义加载器的初始化

Common 类加载器是在 Bootstrap 的 initClassLoaders 初始化的,代码如下。从中可以看到上述所说对应路径是什么。


ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;

private void initClassLoaders() {
    try {
     //创建commonLoader类加载器
        commonLoader = createClassLoader("common"null);
        if (commonLoader == null) {
             commonLoader = this.getClass().getClassLoader();
        }
        //创建catalinaLoader类加载器,父加载器为commonLoader
        catalinaLoader = createClassLoader("server", commonLoader);
        //创建sharedLoader类加载器,父加载器为commonLoader
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

具体还是要看createClassLoader方法。

private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception 
{
    String value = CatalinaProperties.getProperty(name + ".loader");
    //如果没有配置,则返回传入的类加载器
     if ((value == null) || (value.equals("")))
     return parent;
     List<Repository> repositories = new ArrayList<>();
   ..............
    return ClassLoaderFactory.createClassLoader(repositories, parent);
}

上述中省略了不少, CatalinaProperties.getProperty就是从catalina.properties(位于conf目录下)中获取对应值,如下图示例。Tomcat源码五部曲(四)、类加载器获取到值后判断是否为空,为空则返回传入的类加载器,也就是说如果我们没有配置过他的话,Shared、Catalina最终结果都是Common。Tomcat源码五部曲(四)、类加载器不为空则解析出所有路径,添加到List中,最后通过类加载器工厂创建一个类加载并返回。类加载器初始化完毕后,使用catalinaLoader加载创建一个org.apache.catalina.startup.Catalina 对象,最终会调用它的 load 方法,解析 server.xml 初始化容器内部组件。并且Catalina 对象有一个 parentClassLoader 成员变量,它是所有组件的父加载器,默认是 AppClassLoader,Catalina创建完毕后,会反射调用它的 setParentClassLoader 方法,将父加载器设为 sharedLoader。

而 Tomcat 内部顶级容器 Engine 在初始化时,Digester 有一个 SetParentClassLoaderRule 规则,会将 Catalina 的 parentClassLoader 通过 Engine.setParentClassLoader 方法关联起来。

还有Web应用加载器,Web应用类加载器是Web应用私有的,每个Web应用就是一个Context,在Tomcat中,Context的默认实现为StandardContext。所以,创建Web应用类加载器也在StandardContext中。

也就是下面这段代码,每个StandardContext对象都持有一个WebappLoader对象,也就是自己的类加载器,创建后会启动WebappLoader。

protected synchronized void startInternal() throws LifecycleException {
  if (getLoader() == null) {
      WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
      webappLoader.setDelegate(getDelegate());
      setLoader(webappLoader);
  }
}
 
public void setLoader(Loader loader) {
 ......
 //最终会调用WebappLoader的startInternal方法
  ((Lifecycle) loader).start();
}

下面是WebappLoader的startInternal()方法,首先创建类加载器(创建的是ParallelWebappClassLoader,ParallelWebappClassLoader继承WebappClassLoaderBase,这里分析的是Tomcat-9),然后启动类加载器。

 @Override
 protected void startInternal() throws LifecycleException {
   ..........
   //创建类加载器
  classLoader = createClassLoader();
  //启动类加载器
  classLoader.start();

 }

下面是WebappClassLoaderBase的start方法。

@Override
    public void start() throws LifecycleException {
 
        state = LifecycleState.STARTING_PREP;
  //加载web应用的所有class文件
        WebResource classes = resources.getResource("/WEB-INF/classes");
        if (classes.isDirectory() && classes.canRead()) {
            localRepositories.add(classes.getURL());
        }
  //加载web应用lib目录下的jar文件
        WebResource[] jars = resources.listResources("/WEB-INF/lib");
        for (WebResource jar : jars) {
            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
                localRepositories.add(jar.getURL());
                jarModificationTimes.put(
                        jar.getName(), Long.valueOf(jar.getLastModified()));
            }
        }
 
        state = LifecycleState.STARTED;
    }

然后就是我们Servlet类加载过程,这就需要回到StandardWrapper中的loadServlet方法。在这里推荐在web.xml的web-app上设置属性metadata-complete为true,这是由于在ContextConfig的configureStart方法中判断metadata-complete值,如果为false,最终会取反为true,会在此时加载我们的类(但是也无所谓,但是我们想做的是,在第一次访问Servlet的时候去跟踪调试)。

public synchronized Servlet loadServlet() throws ServletException {
  InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
  try {
   //加载我们的Servlet
      servlet = (Servlet) instanceManager.newInstance(servletClass);
  } catch (ClassCastException e) {
  }
}

上述代码首先从父容器StandardContext中获取InstanceManager,InstanceManager在StandardContext的startInternal()方法中创建,其实也就是createInstanceManager()方法。

实现类也就是DefaultInstanceManager,其中持有一个ClassLoader,也就是在上面说的ParallelWebappClassLoader实例。

然后在看DefaultInstanceManager的newInstance方法,核心也在loadClassMaybePrivileged中。

@Override
public Object newInstance(String className) throws IllegalAccessException,
        InvocationTargetException, NamingException, InstantiationException,
        ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, SecurityException 
{
    Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
    return newInstance(clazz.getConstructor().newInstance(), clazz);
}

loadClassMaybePrivileged方法中判断是否开启安全机制,其实还要看loadClass方法。

 protected Class<?> loadClassMaybePrivileged(final String className,
         final ClassLoader classLoader) throws ClassNotFoundException {
     Class<?> clazz;
     //判断是否启动了安全机制
     if (SecurityUtil.isPackageProtectionEnabled()) {
         try {
             clazz = AccessController.doPrivileged(
                     new PrivilegedLoadClass(className, classLoader));
         } catch (PrivilegedActionException e) {
             Throwable t = e.getCause();
             if (t instanceof ClassNotFoundException) {
                 throw (ClassNotFoundException) t;
             }
             throw new RuntimeException(t);
         }
     } else {
      //加载类
         clazz = loadClass(className, classLoader);
     }
     checkAccess(clazz);
     return clazz;
 }

首先判断要加载的类全名开头是不是org.apache.catalina,如果是,则使用containerClassLoader加载。否则使用containerClassLoader尝试再次加载,但是可能会出现ClassNotFoundException异常,如果加载成功,判断他的超类是不是ContainerServlet。如果是,则返回,否则使用classLoader加载,classLoader就是ParallelWebappClassLoader的实例。

 protected Class<?> loadClass(String className, ClassLoader classLoader)
         throws ClassNotFoundException {
     if (className.startsWith("org.apache.catalina")) {
         return containerClassLoader.loadClass(className);
     }
     try {
         Class<?> clazz = containerClassLoader.loadClass(className);
         if (ContainerServlet.class.isAssignableFrom(clazz)) {
             return clazz;
         }
     } catch (Throwable t) {
         ExceptionUtils.handleThrowable(t);
     }
     Class<?> aClass = classLoader.loadClass(className);
     return aClass;
 }

最后就是WebappClassLoaderBase的loadClass方法。

  @Override
  public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
      String name1 = name;
      synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
          Class<?> clazz = null;
          checkStateForClassLoading(name);
           // 从当前ClassLoader的本地缓存中查找,如果不为空则返回
          clazz = findLoadedClass0(name);
          if (clazz != null) {
              if (resolve)
                  resolveClass(clazz);
              return clazz;
          }
           // 查看jvm是否已经加载过此类,如果不为空则返回
          clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
          if (clazz != null) {
               if (resolve)
                  resolveClass(clazz);
              return clazz;
          }
          //通过系统的类加载器加载此类,这里防止应用写的类覆盖了J2SE的类
          String resourceName = binaryNameToPath(name, false);
          ClassLoader javaseLoader = getJavaseClassLoader();
          boolean tryLoadingFromJavaseLoader;
          try {
              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) {
              ExceptionUtils.handleThrowable(t);
              tryLoadingFromJavaseLoader = true;
          }
          if (tryLoadingFromJavaseLoader) {
              try {
                  clazz = javaseLoader.loadClass(name);
                  if (clazz != null) {
                      if (resolve)
                          resolveClass(clazz);
                      return clazz;
                  }
              } catch (ClassNotFoundException e) {
                  // Ignore
              }
          }
        //使用 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);
                  }
              }
          }
         //判断是否需要委托给父类加载器进行加载,delegateLoad一般结果为false
          boolean delegateLoad = delegate || filter(name, true);
          if (delegateLoad) {
              try {
                  clazz = Class.forName(name, false, parent);
                  if (clazz != null) {
                      if (resolve)
                          resolveClass(clazz);
                      return clazz;
                  }
              } catch (ClassNotFoundException e) {
              }
          }
          try {
          //调用findClass方法在webapp级别进行加载
              clazz = findClass(name);
              if (clazz != null) {
                  if (resolve)
                      resolveClass(clazz);
                  return clazz;
              }
          } catch (ClassNotFoundException e) {
              // Ignore
          }
         //如果还是没有加载到类,并且不采用委托机制的话,则通过父类加载器去加载。
          if (!delegateLoad) {
              try {
                  clazz = Class.forName(name, false, parent);
                  if (clazz != null) {
                      if (resolve)
                          resolveClass(clazz);
                      return clazz;
                  }
              } catch (ClassNotFoundException e) {
              }
          }
      }
     //抛出异常
      throw new ClassNotFoundException(name);
  }

可以看到这个类加载器和Java中的双亲委派模型有点不一样,Java默认的双亲委派加载过程如下:

1.从缓存中加载。

2.缓存中不存在则从父类加载器中加载。

3.父类加载器如果加载不了,则从当前类加载器加载。

4.如果还没有,则抛出异常。

而Tomcat提供的Web应用类加载器则不然,当类加载时,除JVM基础类库外,首先尝试当前类加载,然后才进行委派,他的过程大概如下:

1.从缓存中加载。

2.如果没有,则从从JVM的Bootstrap类加载器加载。

3.如果没有,则从当前类加载器加载,顺序先WEB-INF/classes后WEB-INF/lib。

4.如果没有,则从父类加载器加载,由于父类加载采用默认的委派模式,所以顺序为System、Common、Shared。

但上述代码中提到了delegate属性,用于控制是否启用Java委派模式,默认为false,当配置为true时,Tomcat将使用默认的委派模式,过程如下:

1.从缓存中加载。

2.如果没有,则从从JVM的Bootstrap类加载器加载。

3.如果没有,则从父类加载器加载,System、Common、Shared。

4.如果没有,则从当前类加载器加载。


原文始发于微信公众号(十四个字节):Tomcat源码五部曲(四)、类加载器