Разница между загрузкой класса с использованием ClassLoader и Class.forName - PullRequest
23 голосов
/ 26 ноября 2010

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

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

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

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

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

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

Ответы [ 9 ]

48 голосов
/ 18 августа 2011

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

Однако они не отвечают на ваш прямой вопрос: «В чем разница между вышеупомянутыми подходами?», Который имеет дело с одной конкретной перегрузкой 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 не будет знать, какую реализацию использовать, как если бы вы не загружали класс).

Так вот, в чем разница между двумя методами, которые вы просили.

14 голосов
/ 26 ноября 2010

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

С явным подходом 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-парсерабиблиотека, завернутая в него.Ваш код обычно использует эти классы, но в одном случае вам нужно извлечь класс десериализации из более ранней версии библиотеки (в которой сервер приложений находится в его общем ClassLoader).Таким образом, вы могли бы ссылаться на этот Application Server ClassLoader.

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

12 голосов
/ 26 ноября 2010

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

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

Приведенный выше код будет загружать TargetClass ВСЕГДА с системным загрузчиком классов .

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

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

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

Если вам нужно загрузить И инициализировать, используйте Class.forName(String, true, ClassLoader).

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

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

В целом, наиболее надежным и протестированным будет использовать 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 () - это рекомендуемый подход (когда вам нужно загрузить классы из определенного загрузчика классов).

1 голос
/ 04 января 2014

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()); 
      }
}

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

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

time = 1388864219803
java.lang.ClassNotFoundException: com.A
1 голос
/ 26 ноября 2010

Из API doc :

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

  Class.forName(className, true, currentLoader)

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

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

1 голос
/ 26 ноября 2010

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

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

0 голосов
/ 01 сентября 2015

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

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

0 голосов
/ 23 декабря 2010

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

0 голосов
/ 26 ноября 2010

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

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

. Вот что говорит JavaDoc:

forName(String name, boolean initialize, ClassLoader loader)

Для загрузки используется указанный загрузчик классовкласс или интерфейс.Если параметр загрузки имеет значение null, класс загружается через загрузчик класса начальной загрузки.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...