Подтвердить что ты не робот

Разница между загрузкой класса с использованием ClassLoader и Class.forName

Ниже приведены 2 фрагмента кода

Первый использует класс ClassLoader для загрузки указанного класса

ClassLoader cls = ClassLoader.getSystemClassLoader(); Class someClass = cls.loadClass("TargetClass");

Второй использует Class.forName() для загрузки указанного класса

Class cls = Class.forName("TargetClass");

В чем разница между вышеупомянутыми подходами. Какой из них служит для этой цели?

4b9b3361

Ответ 1

Быстрый ответ (без примеров кода)

При явном подходе ClassLoader cls = <a ClassLoader>; у вас есть возможность загрузить класс из ClassLoader, который не является вашим классом ClassLoader по умолчанию. В вашем случае вы используете System ClassLoader по умолчанию, поэтому он дает аналогичный общий результат (с созданием конечной разности объектов) в качестве вызова Class.forName(String name), но вместо этого вы можете ссылаться на другой ClassLoader.

Тем не менее, вы также можете использовать Class.forName(String name, boolean initialize, ClassLoader loader), пока знаете, что такое ClassLoader.

Например, ваше приложение на основе EAR имеет свой собственный ClassLoader с версией библиотеки XML Parsing, завернутой внутри нее. Обычно ваш код использует эти классы, но в одном случае вам нужно взять класс десериализации из более ранней версии библиотеки (которую Application Server находится в общем классе ClassLoader). Таким образом, вы можете ссылаться на этот Application Server ClassLoader.

К сожалению, пока мы не получим проект Jigsaw (JDK 8), это используется чаще, чем мы хотели бы: -)

Ответ 2

Другие ответы очень полные, поскольку они исследуют другие перегрузки Class.forName(...) и говорят о возможности использования разных ClassLoaders.

Однако они не могут ответить на ваш прямой вопрос: "В чем разница между вышеупомянутыми подходами?", который касается одной конкретной перегрузки Class.forName(...). И они пропускают одно очень важное различие. Инициализация класса.

Рассмотрим следующий класс:

public class A {
  static { System.out.println("time = " + System.currentTimeMillis()); }
}

Теперь рассмотрим следующие два метода:

public class Main1 {
  public static void main(String... args) throws Throwable {
    final Class<?> c = Class.forName("A");
  }
}

public class Main2 {
  public static void main(String... args) throws Throwable {
    ClassLoader.getSystemClassLoader().loadClass("A");
  }
}

Первый класс, Main1, при запуске, выдаст такой вывод, как

time = 1313614183558

Другой, тем не менее, не даст никакого вывода. Это означает, что класс A, хотя и загружен, не был инициализирован (т.е. Он <clinit> не был вызван). На самом деле вы даже можете запросить членов класса через отражение перед инициализацией!

Зачем вам это важно?

Существуют классы, выполняющие какую-то важную инициализацию или регистрацию при инициализации.

Например, JDBC указывает интерфейсы, которые реализуются разными поставщиками. Чтобы использовать MySQL, вы обычно делаете Class.forName("com.mysql.jdbc.Driver");. То есть вы загружаете и инициализируете класс. Я никогда не видел этот код, но, очевидно, статический конструктор этого класса должен зарегистрировать класс (или что-то еще) где-нибудь с JDBC.

Если вы сделали ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");, вы не сможете использовать JDBC, так как класс, загруженный, не был инициализирован (а затем JDBC не будет знать, какую реализацию использовать, как если бы вы не загрузили класс).

Итак, в этом разница между двумя запрошенными вами методами.

Ответ 3

В вашем конкретном случае:

ClassLoader cls = ClassLoader.getSystemClassLoader();
Class someClass = cls.loadClass("TargetClass");

Над кодом загрузится TargetClass ВСЕГДА с системный загрузчик классов.

Class cls = Class.forName("TargetClass");

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

Какой из них использовать? Я бы рекомендовал использовать специальный загрузчик классов (ClassLoader.loadClass()) - он ставит вас под контроль и помогает избежать потенциально скрытых проблем между различными средами.

Как найти правильный загрузчик классов? Это зависит от вашей среды:

  • Если вы используете приложение командной строки, вы можете просто использовать системный загрузчик классов или загрузчик классов который загрузил ваши классы приложений (Class.getClassLoader()).
  • если вы работаете внутри управляемой среды (JavaEE, контейнер сервлетов и т.д.), лучше всего проверить current сначала загрузите загрузчик контекста потока, а затем вернитесь к параметрам, указанным в предыдущей точке.
  • или просто используйте собственный пользовательский загрузчик классов (если вы в этом верите)

В общем случае самый проверенный с ошибкой и тестируемый будет использовать ClassUtils.forName() из Spring (см. JavaDoc).

Более подробное объяснение:


Наиболее распространенная форма Class.forName(), которая принимает единственный параметр String, всегда использует загрузчик классов вызывающего. Это загрузчик классов, который загружает код, выполняющий метод forName(). Для сравнения, ClassLoader.loadClass() является методом экземпляра и требует, чтобы вы выбрали конкретного загрузчика классов, который может быть или не быть загрузчиком, который загружает этот код вызова. Если для вашего дизайна важно выбрать определенный загрузчик для загрузки класса, вы должны использовать ClassLoader.loadClass() или трехпараметрическую версию forName(), добавленную в Java 2 Platform, Standard Edition (J2SE): Class.forName(String, boolean, ClassLoader).

Источник: В чем разница между Class.forName() и ClassLoader.loadClass()?


Кроме того, SPR-2611 выделяет один интересный неясный краевой случай при использовании Class.forName(String, boolean, ClassLoader).

Как видно из этой проблемы Spring, использование ClassLoader.loadClass() - это рекомендуемый подход (когда вам нужно загружать классы из определенного загрузчика классов).

Ответ 4

ClassLoader.loadClass() использует указанный загрузчик классов (системный загрузчик классов в вашем случае), тогда как Class.forName() использует загрузчик классов текущего класса.

Class.forName() может использоваться, когда вам не нужен конкретный загрузчик классов и требуется такое же поведение при загрузке классов, что и для статически связанных классов.

Ответ 5

Из API doc:

Вызов этого метода эквивалентен:

  Class.forName(className, true, currentLoader)

где currentLoader обозначает определяющий загрузчик классов текущего класс.

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

Ответ 6

Второй подход загружает класс, используя ClassLoader

 public static Class<?> forName(String className) 
                throws ClassNotFoundException {
        return forName0(className, true, ClassLoader.getCallerClassLoader());

Это то, что говорит JavaDoc:

forName(String name, boolean initialize, ClassLoader loader)

Указанный загрузчик классов используется для загрузите класс или интерфейс. Если параметр loader равен нулю, класс загружается через класс начальной загрузки Загрузчик.

Итак, второй вариант использует System ClassLoader (который, по сути, является тем, что он делает в первом варианте).

Ответ 7

также существует разница при загрузке типов массивов. Я думаю, что classloader.loadClass(clazz) не может обрабатывать типы массивов, но Class.forName(clazz,true,classloader) может.

Ответ 8

ClassLoader.loadClass() всегда загружает системный загрузчик классов, тогда как Class.forName() загружает любой класс. Давайте посмотрим на этот пример,

package com;
public class TimeA {
      public static void main (String args[]) {
            try {
                final Class c = Class.forName("com.A");
                ClassLoader.getSystemClassLoader().loadClass("com.A");
            }catch(ClassNotFoundException ex) {
                System.out.println(ex.toString());
            }
      }
}

class A {
      static {
          System.out.println("time = " + System.currentTimeMillis()); 
      }
}

Когда yoy запускает эту программу, вы получите исключение в ClassLoader.getSystemClassLoader().loadClass("com.A");

Вывод может быть:

time = 1388864219803
java.lang.ClassNotFoundException: com.A

Ответ 9

Но статический блок инициализатора выполняется только тогда, когда мы используем class.forname( "..." );

Я только что протестировал.