Реализация загрузчика классов Tomcat

Tomcat

Tomcat внутри определяет несколько загрузчиков классов, чтобы приложения и контейнеры могли получать доступ к классам и ресурсам в разных репозиториях и в то же время достигать цели изоляции классов между приложениями. Эта статья была впервые опубликована в публичном аккаунте:Исходный код Крещения.

1. Механизм загрузки классов Java

Загрузка класса — это загрузка скомпилированного файла класса в память JVM (постоянная генерация/метапространство).

Причина, по которой загрузчик классов может достичьклассовая сегрегация, потому что два класса равны, если они загружены одним и тем же загрузчиком классов, иначе они не должны быть равны.

Когда JVM загружается, она используетМеханизм родительского делегирования, когда загрузчик класса хочет загрузить класс, порядок загрузки следующий:

  • Сначала делегируйте запрос родительскому загрузчику, если родительский загрузчик не может найти класс для загрузки.
  • Затем найдите свой собственный репозиторий и попробуйте его загрузить.

Преимущество этого механизма в том, что он может гарантироватьОсновная библиотека классов не перезаписывается.

Согласно рекомендации спецификации сервлета, загрузчик веб-приложения немного отличается. Сначала он будет искать в своей собственной библиотеке ресурсов, а не делегировать вверх, нарушая стандартный механизм делегирования. Давайте посмотрим на дизайн и реализацию Tomcat.

2. Дизайн загрузчика класса Tomcat

Общая структура загрузчика классов Tomcat выглядит следующим образом:

Tomcat 类加载器结构

Загрузчики классов, предоставляемые внутри JDK:

  • Bootstrap- Запустите загрузчик классов, который является частью JVM и загружает определенные файлы в каталог /lib/.
  • Extension- Расширьте загрузчик классов и загрузите библиотеку классов в каталог /lib/ext/.
  • Application- Загрузчик классов приложения, также называемый системным загрузчиком классов, загружает библиотеку классов, указанную в CLASSPATH.

Загрузчики классов для пользовательской реализации Tomcat:

  • Common- Родительским загрузчиком является AppClassLoader, который по умолчанию загружает библиотеку классов в каталог ${catalina.home}/lib/.
  • Catalina- Родительский загрузчик — это общий загрузчик классов, который загружает ресурсы, настроенные server.loader в файле конфигурации catalina.properties, обычно это ресурсы, используемые внутри Tomcat.
  • Shared— Родительский загрузчик — это общий загрузчик классов, который загружает ресурсы, настроенные с помощью shared.loader в файле конфигурации catalina.properties, обычно это ресурсы, используемые всеми веб-приложениями.
  • WebappX— Родительский загрузчик — это общий загрузчик, который загружает классы в /WEB-INF/classes и jar-файлы в /WEB-INF/lib/.
  • JasperLoader- Родительский загрузчик — это загрузчик веб-приложения, который загружает файл класса, сгенерированный путем компиляции JSP, в рабочий каталог.

При реализации приведенная выше диаграмма представляет собой не отношения наследования, а отношения родитель-потомок через комбинацию. Диаграмма исходного класса загрузчика классов Tomcat:

Tomcat 类加载器类图

Common, Catalina, Shared — все экземпляры StandardClassLoader, и по умолчанию они относятся к одному и тому же объекту. Среди них StandardClassLoader ничем не отличается от URLClassLoader, WebappClassLoader реализует следующий поиск и загрузку по спецификации:

  1. Загружается из репозитория Bootstrap внутри JVM
  2. Загрузка из пути загрузчика приложения, т. е. CLASSPATH
  3. Из каталога /WEB-INF/classes в веб-программе
  4. Из файла jar в /WEB-INF/lib внутри веб-программы
  5. Загружается из контейнера. Общий репозиторий загрузчика, который является общим ресурсом для всех веб-программ.

Затем посмотрите на реализацию исходного кода.

3. Инициализация пользовательского загрузчика

Общий загрузчик классов инициализируется в initClassLoaders Bootstrap.Исходный код выглядит следующим образом:

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();
    }
    // 指定仓库路径配置文件前缀和父加载器,创建 ClassLoader 实例
    catalinaLoader = createClassLoader("server", commonLoader);
    sharedLoader = createClassLoader("shared", commonLoader);
  } catch (Throwable t) {
    log.error("Class loader creation threw exception", t);
    System.exit(1);
  }
}

Видно, что были созданы три загрузчика классов, createClassLoader должен получить адрес хранилища ресурсов в соответствии с конфигурацией и, наконец, возвращает экземпляр StandardClassLoader.Основной код выглядит следующим образом:

private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception {

    String value = CatalinaProperties.getProperty(name + ".loader");
    if ((value == null) || (value.equals("")))
        return parent; // 如果没有配置,则返回传入的父加载器
    ArrayList repositoryLocations = new ArrayList();
    ArrayList repositoryTypes = new ArrayList();
    ...
    // 获取资源仓库路径
    String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
    Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);
    // 创建一个 StandardClassLoader 对象
    ClassLoader classLoader = ClassLoaderFactory.createClassLoader
            (locations, types, parent);
    ...
    return classLoader;
}

После инициализации загрузчик классов создаетCatalinaобъект, который в конечном итоге вызовет свой метод загрузки для анализа server.xml для инициализации внутренних компонентов контейнера. Итак, как контейнер, такой как Engine, связан с родительским загрузчиком этой настройки?

Объект Catalina имеет переменную-член parentClassLoader, которая является родительским загрузчиком всех компонентов.По умолчанию используется AppClassLoader.При создании объекта его метод setParentClassLoader будет вызываться путем отражения, а родительский загрузчик будет установлен на sharedLoader.

Когда контейнер верхнего уровня Engine внутри Tomcat инициализируется, Digester имеет правило SetParentClassLoaderRule, которое связывает parentClassLoader Catalina через метод Engine.setParentClassLoader.

4. Как сломать механизм родительского делегирования

Ответ заключается в использовании Thread.getContextClassLoader() -загрузчик контекста для текущего потока, который можно динамически установить во время выполнения кода с помощью Thread.setContextClassLoader() .

По умолчанию загрузчик контекста Thread наследуется от родительского потока, что означает, что загрузчик контекста по умолчанию для всех потоков совпадает с первым запущенным потоком, который является основным потоком, а его загрузчик контекста — AppClassLoader.

Tomcat сначала инициализирует WebappClassLoader при запуске StandardContext, затем устанавливает его в качестве загрузчика контекста текущего потока и, наконец, инкапсулирует его как объект Loader, который используется при загрузке классов сервлетов с помощью отношений родитель-потомок между контейнерами.

5. Загрузка классов для веб-приложений

Загрузка классов веб-приложений выполняется методом loadClass(String, boolean) WebappClassLoader.Основной код выглядит следующим образом:

public synchronized Class loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
  ...
  Class clazz = null;
  // (0) 检查自身内部缓存中是否已经加载
  clazz = findLoadedClass0(name);
  if (clazz != null) {
    if (log.isDebugEnabled())
      log.debug("  Returning class from cache");
    if (resolve) resolveClass(clazz);
    return (clazz);
  }
  // (0.1) 检查 JVM 的缓存中是否已经加载
  clazz = findLoadedClass(name);
  if (clazz != null) {
    if (log.isDebugEnabled())
      log.debug("  Returning class from cache");
    if (resolve) resolveClass(clazz);
    return (clazz);
  }
  // (0.2) 尝试使用系统类加载加载,防止覆盖 J2SE 类
  try {
    clazz = system.loadClass(name);
    if (clazz != null) {
      if (resolve) resolveClass(clazz);
      return (clazz);
    }
  } catch (ClassNotFoundException e) {// Ignore}
  // (0.5) 使用 SecurityManager 检查是否有此类的访问权限
  if (securityManager != null) {
    int i = name.lastIndexOf('.');
    if (i >= 0) {
      try {
        securityManager.checkPackageAccess(name.substring(0,i));
      } catch (SecurityException se) {
        String error = "Security Violation, attempt to use " +
            "Restricted Class: " + name;
        log.info(error, se);
        throw new ClassNotFoundException(error, se);
      }
    }
  }
  boolean delegateLoad = delegate || filter(name);
  // (1) 是否委托给父类,这里默认为 false
  if (delegateLoad) {
      ...
  }
  // (2) 尝试查找自己的存储库并加载
  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) {}
  // (3) 如果此时还加载失败,那么将加载请求委托给父加载器
  if (!delegateLoad) {
    if (log.isDebugEnabled())
      log.debug("  Delegating to parent classloader at end: " + parent);
    ClassLoader loader = parent;
    if (loader == null)
      loader = system;
    try {
      clazz = loader.loadClass(name);
      if (clazz != null) {
        if (log.isDebugEnabled())
          log.debug("  Loading class from parent");
        if (resolve) resolveClass(clazz);
        return (clazz);
      }
    } catch (ClassNotFoundException e) {}
  }
  // 最后加载失败,抛出异常
  throw new ClassNotFoundException(name);
}

в предотвращенииПереопределить классы J2SEВ то время версия Tomcat 6 использовала AppClassLoader, а основная библиотека классов rt.jar загружалась с помощью Bootstrap Classloader, но этот загрузчик нельзя было получить в коде Java, и в более высокой версии были сделаны следующие оптимизации:

ClassLoader j = String.class.getClassLoader();
if (j == null) {
  j = getSystemClassLoader();
  while (j.getParent() != null) {
    j = j.getParent();
  }
}
this.javaseClassLoader = j;

То есть используйте загрузчик классов, максимально приближенный к загрузчику Bootstrap.

6. Резюме

Я полагаю, что большинство людей сталкивались с исключением ClassNotFoundException, которое связано с загрузчиком классов, и имеют определенное понимание принципа загрузки, что полезно для устранения неполадок.

Для лучшего понимания этот класс загрузчика моделируется здесь.

адрес проекта:GitHub.com/Когда я говорю/Rx Том…

Исходный код загрузчика:Loader.java

Поиск в общедоступной учетной записи WeChat "Исходный код Крещения” для более подробного анализа исходного кода и создания колес.