Какие типы загрузчиков классов (ClassLoader) существуют в Java?

Ответ

Загрузчики классов в Java образуют иерархию с чётким порядком делегирования.

Встроенные загрузчики (Иерархия)

  1. Bootstrap ClassLoader (Primordial):

    • Назначение: Загружает основные классы Java Core (java.lang.*, java.util.* и т.д.) из rt.jar и других ключевых библиотек.
    • Особенность: Реализован на нативном коде (часть JVM), не является Java-объектом. В коде отображается как null.
  2. Platform (Extension) ClassLoader:

    • Назначение: Загружает классы из директории расширений JRE (например, jre/lib/ext) или пути, указанного в java.ext.dirs.
    • Родитель: Bootstrap ClassLoader.
    • Реализация: Java-класс, наследник java.net.URLClassLoader.
  3. System (Application) ClassLoader:

    • Назначение: Загружает классы приложения из classpath (параметр -cp или -classpath).
    • Родитель: Platform ClassLoader.
    • Реализация: Также наследник URLClassLoader. Доступен через ClassLoader.getSystemClassLoader().

Пользовательские загрузчики (Custom ClassLoaders)

Разработчик может создавать собственные загрузчики, наследуясь от ClassLoader или URLClassLoader. Это нужно для:

  • Динамической загрузки классов (например, из сети или БД).
  • Реализации изоляции модулей (как в OSGi или сервлет-контейнерах).
  • Hot-swapping кода в runtime.

Пример пользовательского загрузчика:

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 1. Загрузить байт-код класса из нестандартного места
        byte[] classBytes = loadClassDataFromCustomSource(name);
        if (classBytes == null) {
            throw new ClassNotFoundException(name);
        }
        // 2. Преобразовать байты в Class-объект
        return defineClass(name, classBytes, 0, classBytes.length);
    }
    private byte[] loadClassDataFromCustomSource(String name) { /* ... */ }
}

Принцип делегирования (Parent-Delegation Model)

При запросе на загрузку класса загрузчик сначала делегирует запрос своему родителю. Только если родитель не смог загрузить класс, текущий загрузчик пытается сделать это сам через метод findClass(). Это обеспечивает безопасность и предотвращает загрузку пользовательских классов с именами стандартных (например, java.lang.String).

Ответ 18+ 🔞

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

Слушай, как это работает, а то голова сейчас ебёт.

Вот эти, блядь, встроенные царьки:

  1. Bootstrap ClassLoader (Папа-основатель, нативный). Этот, сука, вообще бог. Написан не на Java, а на чём-то нативном, часть самой JVM. Его задача — загрузить самые ядрёные, системные классы вроде java.lang.*. В коде он отображается как null, потому что он выше этой всей Java-суеты, понимаешь? Он с небес спустился, загрузил основы и молчит, как партизан.
  2. Platform (Extension) ClassLoader (Старший сынок). Уже Java-объект, наследник URLClassLoader. Сидит на довольствии — загружает всякие расширения из jre/lib/ext. Если ему что нужно, он сначала к папаше (Bootstrap) обращается: «Батя, а есть у тебя такой класс?». Нету? Ну тогда он сам попробует из своих закромов достать.
  3. System (Application) ClassLoader (Младший сыночек, который всем управляет). Самый известный, его можно вызвать через ClassLoader.getSystemClassLoader(). Это он, сука, твой classpath и грузит. Все твои -cp и -classpath — это его епархия. Родитель у него — Platform ClassLoader. И он тоже первым делом бежит к старшему брату: «Братан, помоги!». А тот уже к отцу.

А теперь, блядь, самое интересное — пользовательские загрузчики. Это когда ты, епта, такой: «А дай-ка я сам покомандую!». Наследуешься от ClassLoader и делаешь что хочешь. Зачем? Да похуй, на самом деле, но если серьёзно:

  • Классы из сети подтянуть, из базы данных, с флешки, которую в жопу воткнули — откуда угодно.
  • Изоляцию сделать, чтобы один модуль в другом модуле ничего не сломал (как в тех ваших OSGi).
  • Hot-swapping, чтобы на лету код менять, хотя это, блядь, отдельная история с приключениями.

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

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 1. Достаём байт-код откуда-нибудь, хоть из облака в жопу
        byte[] classBytes = loadClassDataFromCustomSource(name);
        if (classBytes == null) {
            throw new ClassNotFoundException(name);
        }
        // 2. Из байтов лепим полноценный Class-объект, как из пластилина
        return defineClass(name, classBytes, 0, classBytes.length);
    }
    private byte[] loadClassDataFromCustomSource(String name) { /* ... */ }
}

И главный принцип, блядь, который всё скрепляет — делегирование родителю (Parent-Delegation Model). Это гениально и просто, как тапок.

Когда загрузчику говорят: «Загрузи класс com.example.MyClass», он делает вот что:

  1. НЕ лезет сразу в свои закрома. Вместо этого он такой: «Папа/братан, может, у тебя уже есть этот класс?».
  2. Родитель проделывает ту же хуйню — спрашивает своего родителя. И так до самого верха, до null (Bootstrap).
  3. Если на каком-то уровне сверху нашли класс — отлично, возвращают его. Если все предки разводят руками — только тогда текущий загрузчик начинает искать сам, через свой findClass().

Зачем этот цирк? Да чтобы, блядь, безопасность была! Чтобы ты, хитрожопый, не подсунул свою кривую версию java.lang.String и не устроил пиздец всей виртуальной машине. Системные классы грузятся только сверху, с небес, а твоё самодельное говно — уже в самом низу иерархии.

Вот и вся магия, ёпта. Иерархия, делегирование, и не лезь со своим уставом в чужой монастырь, а то получишь ClassNotFoundException прямо в ебальник.