Прежде чем изучать загрузчик классов в Tomcat и почему Tomcat реализует свой собственный загрузчик классов, чтобы сломать модель родительского делегирования, нам сначала нужно узнать, что такое загрузчик классов, определенный в Java, и какова модель родительского делегирования.
Загрузчик классов в Java
Загрузчик классов отвечает за динамическую загрузку java-файлов в JVM во время работы программы.
С точки зрения виртуальной машины Java есть два разных загрузчика классов:
-
Bootstrap ClassLoader: Этот загрузчик классов реализован на языке C++ и является частью самой виртуальной машины.
-
Другой загрузчик классов: загрузчик классов на языке Java, независимый от внешней виртуальной машины, и все производные от абстрактного класса.
java.lang.ClassLoader
, из которых загрузчики других классов примерно делятся на- ExtensionClassLoader: этот загрузчик классов состоит из
ExtClassLoader
реализация, отвечающая за загрузкуJAVA_HOME/lib/ext
все классы в каталоге илиjava.ext.dir
Все классы в пути, указанном системной переменной. - ApplicationClassLesser: этот класс погрузчик
AppClassLoader
Реализованный, он отвечает за загрузку всех классов, указанных в пользовательском пути к классам (ClassPath).Если приложение не настраивает собственный загрузчик классов, он обычно является загрузчиком классов по умолчанию в программе. - Пользовательский загрузчик: Настройте загрузчик для загрузки определенного пути в соответствии с вашими потребностями.
- ExtensionClassLoader: этот загрузчик классов состоит из
Для любого класса его уникальность в виртуальной машине Java должна быть установлена загружающим его загрузчиком классов и самим классом
Модель родительского делегирования
Иерархия, показанная на диаграмме выше, называется моделью родительского делегирования загрузчика классов. Модель родительского делегирования требует, чтобы все загрузчики, кроме загрузчика класса запуска верхнего уровня, имели свой собственный родительский загрузчик. Отношения родитель-потомок здесь достигаются не наследованием, а установкойparent
переменная для достижения.
Рабочий процесс модели родительского делегирования таков: если получен запрос на загрузку класса, он не будет загружать этот класс первым, а сначала делегирует запрос загрузчику родительского класса для завершения, и так происходит на каждом уровне, пока не В загрузчике только если родительский класс не загрузит этот файл, то дочерний класс попытается загрузить его сам.
Зачем настраивать модель родительского делегирования? По сути, это обеспечение стабильной работы Java-программ, таких как класс Object, который хранится вrt.jar
, независимо от того, какой загрузчик классов хочет загрузить класс Object, он в конечном итоге делегирует BootStrapClassLoader верхнего уровня, поэтому объект, используемый во всех классах, является одним и тем же классом.Наоборот, если нет родительской модели делегирования, то любой загрузчик классов может быть определен новый класс Object, и приложение станет очень запутанным. На самом деле код модели родительского делегирования очень прост. Реализовано в методе loadClass в ClassLoader.
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查请求类是否被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果没被本类类加载器加载过,先委托给父类进行加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果没有父类,则表明在顶层,就交给BootStrap类加载器加载
c = findBootstrapClassOrNull(name);
}
// 如果最顶层的类也找不到,那么就会抛出ClassNotFoundException异常
} catch (ClassNotFoundException e) {
}
// 如果父类都没有加载过此类,子类才开始加载此类
if (c == null) {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
Мы видим, чтоfindClass
Методы — это логика, которая должна быть реализована самими подклассами.
Загрузчики классов в Tomcat
Следующая диаграмма представляет собой схему загрузчика классов Tomcat, приведенную в официальной документации версии Tomcat9.
Bootstrap
|
System
|
Common
/ \
Webapp1 Webapp2 ..
- Bootstrap: это самый высокий загрузчик Java, реализованный на языке C, в основном используемый для загрузки основных классов, необходимых при запуске JVM, таких как
$JAVA_HOME/jre/lib/ext
класс под путем. - Система: загрузится
CLASSPATH
Все классы в пути, определенном системной переменной. - Общее: будут загружены все классы в файле lib по пути Tomcat.
- Webapp1, Webapp2...: будут загружены все классы в проекте по пути веб-приложения. Проект соответствует WebappClassLoader, реализующему изоляцию классов между приложениями.
Эти три части отражены на приведенной выше схеме модели родительского делегирования Java. Однако видно, что ExtClassLoader не отрисовывается, можно понять, что он слит с бутстрапом.JAVA_HOME/jre/lib
Загрузите класс ниже. Итак, почему Tomcat хочет настроить загрузчик классов?
- Изолируйте разные приложения: разные приложения A и B развернуты в одном и том же Tomcat, например, A использует Spring 2.5. B использует Spring 3.5, тогда, если два приложения используют один и тот же загрузчик классов, веб-приложение не запустится из-за покрытия пакета jar.
- Гибкость: загрузчики классов между веб-приложениями независимы друг от друга, поэтому вы можете перестроить разные загрузчики классов, чтобы заменить исходные в соответствии с измененными файлами. Чтобы другие приложения не пострадали.
- Производительность. Если в Tomcat развернуто несколько приложений, у нескольких приложений будут одинаковые зависимости библиотеки классов. Затем ту же библиотеку классов можно загрузить с помощью загрузчика классов Common.
Tomcat настраивает загрузчик классов WebAppClassLoader. Нарушение механизма родительского делегирования,То есть, если получен запрос на загрузку класса, он попытается загрузить его сам, а если его не удастся найти, он будет передан родительскому загрузчику для его загрузки., цель состоит в предпочтительной загрузке классов, определенных самим веб-приложением. Мы знаем, что метод loadClass по умолчанию ClassLoader загружает классы в модели родительского делегирования. Поскольку Tomcat хочет нарушить это правило, он должен переписать метод loadClass. Мы можем посмотреть на переписанный метод loadClass в классе WebAppClassLoader.
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
// 1. 从本地缓存中查找是否加载过此类
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// 2. 从AppClassLoader中查找是否加载过此类
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
String resourceName = binaryNameToPath(name, false);
// 3. 尝试用ExtClassLoader 类加载器加载类,防止Web应用覆盖JRE的核心类
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
tryLoadingFromJavaseLoader = true;
}
boolean delegateLoad = delegate || filter(name, true);
// 4. 判断是否设置了delegate属性,如果设置为true那么就按照双亲委派机制加载类
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// 5. 默认是设置delegate是false的,那么就会先用WebAppClassLoader进行加载
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
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) {
// Ignore
}
// 6. 如果此时在WebAppClassLoader没找到类,那么就委托给AppClassLoader去加载
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
Наконец, резюмируя слова с официального сайта Tomcat:
Порядок загрузки классов по умолчанию для веб-приложений (нарушение правила родительского делегирования):
- Первая загрузка из BootStrapClassLoader JVM.
- Загрузите веб-приложение под
/WEB-INF/classes
класс в . - Загрузите веб-приложение под
/WEB-INF/lib/*.jap
Классы в пакете jar в . - Загрузите классы по системному пути, указанному выше.
- Загрузите классы по общему пути, указанному выше.
Если настроено в конфигурационном файле<Loader delegate="true"/>
, то соблюдается правило родительского делегирования, и порядок загрузки следующий:
- Первая загрузка из BootStrapClassLoader JVM.
- Загрузите классы по системному пути, указанному выше.
- Загрузите классы по общему пути, указанному выше.
- Загрузите веб-приложение под
/WEB-INF/classes
класс в . - Загрузите веб-приложение под
/WEB-INF/lib/*.jap
Классы в пакете jar в .