Classloader дінамічкеская завантаження класів - dmitriymx blog

ClassLoader: дінамічкеская завантаження класів

Classloader дінамічкеская завантаження класів - dmitriymx blog

Ось вам просте запитання: як ідентифікуються класи в JVM. Правильна відповідь: по повному імені класу, що складається з імені пакета і власного імені класу. З цього напрошується висновок, що в програмі не може існувати два класи з однаковими повними іменами. Однак, це не так. Як і в початковій школі, де нас вчили правилом «на нуль ділити не можна», а потім в старших класах розповіли про нескінченність, так і в Java є «маленька брехня».

Насправді, класи ідентифікуються по імені пакета, власного імені класу і. завантажувачу, ClassLoader 'у. Таким чином, в програмі можуть існувати два класи з однаковими повними іменами і не конфліктувати один з одним.

Про це та інші смакоти ClassLoader 'а ми сьогодні і поговоримо.

Однією з основних особливостей Java є модель динамічного завантаження класів, яка дозволяє завантажувати виконуваний код в JRE без перезавантаження основне додаток. Будь-клас, який використовується в середовищі виконання так чи інакше був завантажений будь-яким загрузчиком в Java. До початку виконання програми, створено три основних завантажувача:

  • Bootstrap ClassLoader - базовий завантажувач;
  • Extension Classloader - завантажувач розширень;
  • System Classloader - системний завантажувач.

Bootstrap ClassLoader реалізований на рівні JVM і не має зворотного зв'язку з середовищем виконання. Даним загрузчиком завантажуються класи з директорії $ JAVA_HOME / lib і всі базові класи. Тому, спроба отримання завантажувача у класів java. * Завжди закінчується null'ом. Але якщо дуже хочеться, то управляти завантаженням базових класів можна за допомогою ключа -Xbootclasspath. який дозволяє перевизначати набори базових класів.

Extension Classloader завантажує класи з директорії $ JAVA_HOME / lib / ext. В Sun JRE - це клас sun.misc.Launcher $ ExtClassLoader. Управляти завантаженням розширень можна за допомогою системної опції java.ext.dirs.

System Classloader реалізований вже на рівні JRE. В Sun JRE - це клас sun.misc.Launcher $ AppClassLoader. Цим загрузчиком завантажуються класи, шляхи до яких вказані в змінній оточення CLASSPATH. Управляти завантаженням системних класів можна за допомогою ключа -classpath або системної опцією

Завантажники класів утворюють ієрархію, коренем якої є базовий завантажувач. Всі інші завантажувачі при ініціалізації зберігають посилання на батьківський завантажувач. Таким чином досягається реалізація моделі делегування завантаження.

Classloader дінамічкеская завантаження класів - dmitriymx blog

Право завантаження класу рекурсивно делегується від самого нижнього завантажувача в ієрархії до самого верхнього. Такий підхід дозволяє завантажувати класи тим загрузчиком, який максимально близько знаходиться до базового. Так досягається максимальна область видимості класів. Під областю видимості мається на увазі наступне: кожен завантажувач веде облік класів, які були їм завантажені. Безліч цих класів і має назву областю видимості. При цьому завантажувач бачить тільки «свої» класи і класи «батька» і поняття не має про класах, які були завантажені його «нащадком».

Розглянемо процес завантаження більш детально. Нехай під час виконання програми зустрілася декларація змінної використовує клас Bagira. Тоді процес пошуку і завантаження класу буде таким:

Classloader дінамічкеская завантаження класів - dmitriymx blog

Тепер подумаємо: який клас буде реально завантажений, якщо в $ JAVA_HOME / lib / ext і в CLASSPATH є класи з однаковими повними іменами? Правильно, клас з $ JAVA_HOME / lib / ext. а на CLASSPATH ніхто не подивиться. Хоча з ним теж не все просто: класи завантажуються в тому порядку, в якому вони були вказані в CLASSPATH. За цим якщо вказати два jar-файлу, наприклад A.jar і B.jar, що містять однакові класи, то в пам'ять завантажиться клас з A.jar, а клас з B.jar буде пропущено

Повторюся: кожен ClassLoader бачить «свої» класи і класи «батька». Класи «нащадків», ні тим більше класи «парралельно» загрузчиков, ClassLoader не бачить. Більш того, для JVM - це різні класи. При спробі привести один клас до іншого викличе виключення java.lang.ClassCastException. навіть якщо у них і збігаються повні імена.

Тепер від слів до справи. Якщо ми збираємося створити свій завантажувач класів, то важливо пам'ятати наступне:

  • завантажувач повинен явно або неявно розширювати клас java.lang.ClassLoader;
  • завантажувач повинен підтримувати модель делегування завантаження, утворюючи ієрархію. Якщо цього не зробити, можуть виникнути проблеми з областю видимості;
  • в класі java.lang.ClassLoader вже реалізований метод безпосереднього завантаження - defineClass (). який байт-код перетворює в java.lang.Class. здійснюючи його валідацію;
  • механізм рекурсивного пошуку також реалізований в класі java.lang.ClassLoader і піклуватися про це не потрібно;
  • для коректної реалізації завантажувача досить лише перевизначити метод loadClass () класу java.lang.ClassLoader.

Наш завантажувач буде завантажувати jar-файли не через java.net.URLClassLoader. а «вручну». Так буде наочніше розглянути весь процес. А вантажити ми будемо плагіни, які будуть розташовуватися в папці plugins / і інтерфейс для яких потрібно описати:

Тепер напишемо наш завантажувач. Правда ми тут трохи смухлюем і завантажимо відразу все класи плагіна в пам'ять:

Тепер напишемо основне додаток, що використовує наш завантажувач

Відмінно. Залишилося написати сам плагін:

Упакуємо наш плагін в jar і збережемо в папці plugins /.

Так, ми трохи «зшахраювати», жорстко вказавши в основному додатку який клас вважати за точку входу, але це ніяк не позначиться на загальній картині, що відбувається. Наша програма успішно буде завантажувати плагін і виконувати його метод run ().

Оу, мало не забув. «. в програмі можуть існувати два класи з однаковим повним ім'ям і не конфліктувати один з одним. ». пам'ятаю-пам'ятаю, зараз продемонструю.

Вуаля, у нас працюють два однакових класу і при цьому не конфліктують. Однак, якщо ми спробуємо зробити ось так: то JVM пошле нас куди подалі (java.lang.ClassCastException), тому як для JVM це два абсолютно різних класу.