Статья длинная, но в ней от начала и до конца описан принцип исполняемого jar-пакета SpringBoot, терпеливо смотрите. В то же время статья основана на
SpringBoot-2.1.3
анализ. Задействованные точки знаний в основном включаютЖизненный цикл Maven и настраиваемые плагины, JDK предоставляет классы инструментов для пакетов jar и способы расширения Springboot, и, наконец, настраиваемые загрузчики классов..
spring-boot-maven-plugin
Исполняемый jar-пакет SpringBoot также известен какfat jar
,ДаОн включает в себя все сторонние зависимые банки, Пакет JAR встраивает все зависимости, кроме виртуальной машины Java, которая является All-In-One JAR Package. Общий плагинmaven-jar-plugin
сгенерированный пакет иspring-boot-maven-plugin
Прямая разница между сгенерированными пакетамиfat jar
В основном добавлены две части, первая частькаталог библиотеки, в котором хранятся файлы пакетов jar, от которых зависит Maven. Вторая частьspring boot loader
родственные классы.
fat jar 目录结构
├─BOOT-INF
│ ├─classes
│ └─lib
├─META-INF
│ ├─maven
│ ├─app.properties
│ ├─MANIFEST.MF
└─org
└─springframework
└─boot
└─loader
├─archive
├─data
├─jar
└─util
Что вы хотите знатьfat jar
Как это сгенерируется, нужно знатьspring-boot-maven-plugin
рабочий механизм иspring-boot-maven-plugin
Это настраиваемый плагин, поэтому мы должны знать,Как работают пользовательские плагины Maven
Пользовательский плагин Maven
Maven имеет три независимых жизненных цикла:clean,defaultиsite, а каждый жизненный цикл содержит некоторыеphaseСтадии, стадии последовательны, а более поздние стадии зависят от более ранних стадий. стадия жизненного циклаphaseЦели с плагинамиgoalСвяжите друг друга, чтобы выполнить фактическую задачу сборки.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
repackage
В соответствии с целью будет выполненоorg.springframework.boot.maven.RepackageMojo#execute
, основная логика этого метода заключается в вызовеorg.springframework.boot.maven.RepackageMojo#repackage
private void repackage() throws MojoExecutionException {
//获取使用maven-jar-plugin生成的jar,最终的命名将加上.orignal后缀
Artifact source = getSourceArtifact();
//最终文件,即Fat jar
File target = getTargetFile();
//获取重新打包器,将重新打包成可执行jar文件
Repackager repackager = getRepackager(source.getFile());
//查找并过滤项目运行时依赖的jar
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
getFilters(getAdditionalFilters()));
//将artifacts转换成libraries
Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
getLog());
try {
//提供Spring Boot启动脚本
LaunchScript launchScript = getLaunchScript();
//执行重新打包逻辑,生成最后fat jar
repackager.repackage(target, libraries, launchScript);
}
catch (IOException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
//将source更新成 xxx.jar.orignal文件
updateArtifact(source, target, repackager.getBackupFile());
}
давайте позаботимсяorg.springframework.boot.maven.RepackageMojo#getRepackager
знаю этот методRepackager
Как он генерируется, можно примерно предположить внутреннюю логику упаковки.
private Repackager getRepackager(File source) {
Repackager repackager = new Repackager(source, this.layoutFactory);
repackager.addMainClassTimeoutWarningListener(
new LoggingMainClassTimeoutWarningListener());
//设置main class的名称,如果不指定的话则会查找第一个包含main方法的类,repacke最后将会设置org.springframework.boot.loader.JarLauncher
repackager.setMainClass(this.mainClass);
if (this.layout != null) {
getLog().info("Layout: " + this.layout);
//重点关心下layout 最终返回了 org.springframework.boot.loader.tools.Layouts.Jar
repackager.setLayout(this.layout.layout());
}
return repackager;
}
/**
* Executable JAR layout.
*/
public static class Jar implements RepackagingLayout {
@Override
public String getLauncherClassName() {
return "org.springframework.boot.loader.JarLauncher";
}
@Override
public String getLibraryDestination(String libraryName, LibraryScope scope) {
return "BOOT-INF/lib/";
}
@Override
public String getClassesLocation() {
return "";
}
@Override
public String getRepackagedClassesLocation() {
return "BOOT-INF/classes/";
}
@Override
public boolean isExecutable() {
return true;
}
}
layout
Мы можем перевести его в макет файла или макет каталога, код ясен и ясен, и нам нужно обратить внимание, что также является следующим фокусом внимания.org.springframework.boot.loader.JarLauncher
, исходя из имени, которое, скорее всего, вернет исполняемый файлjar
Класс запуска для файла.
Содержимое файла MANIFEST.MF
Manifest-Version: 1.0
Implementation-Title: oneday-auth-server
Implementation-Version: 1.0.0-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: oneday
Implementation-Vendor-Id: com.oneday
Spring-Boot-Version: 2.1.3.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.oneday.auth.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_171
repackager
Сгенерированный файл MANIFEST.MF представляет собой приведенную выше информацию, вы можете увидеть две ключевые сведения.Main-Class
иStart-Class
. Мы можем пойти дальше, запись запуска программы не определена в нашем SpringBootmain
, ноJarLauncher#main
, а затем используйте отражение для вызова определенногоStart-Class
изmain
метод
JarLauncher
Знакомство с ключевыми классами
-
java.util.jar.JarFile
Чтение предоставлено классами инструментов JDKjar
документ -
org.springframework.boot.loader.jar.JarFile
Спрингбот-погрузчик наследует от JDKJarFile
своего рода -
java.util.jar.JarEntry
Запись файла ``jar``, предоставляемая классом инструментов DK. -
org.springframework.boot.loader.jar.JarEntry
Springboot-loader наследует предоставленный JDKJarEntry
своего рода -
org.springframework.boot.loader.archive.Archive
Унифицированный уровень ресурсов доступа, абстрагированный Springboot-
JarFileArchive
Абстракция файлов пакетов jar -
ExplodedArchive
Каталог файлов
-
Вот бы описатьJarFile
роль, каждыйJarFileArchive
будет соответствовать одномуJarFile
. Во время строительства внутренняя структура будет разобрана для полученияjar
каждый в сумкедокументилипапкасвоего рода. Мы можем взглянуть на аннотации для этого класса.
/* Extended variant of {@link java.util.jar.JarFile} that behaves in the same way but
* offers the following additional functionality.
* <ul>
* <li>A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} based
* on any directory entry.</li>
* <li>A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} for
* embedded JAR files (as long as their entry is not compressed).</li>
**/ </ul>
jar
Разделитель ресурсов в!/
, представленный в JDKJarFile
URL-адрес поддерживает только один '!/', а Spring boot расширяет этот протокол для поддержки нескольких '!/', которые могут представлять jar в jar, jar в каталоге и толстые ресурсы jar.
Пользовательский механизм загрузки классов
- Самый простой: Bootstrap ClassLoader (загружает классы в каталог /lib JDK)
- Sub-basic: Extension ClassLoader (загружает классы в каталог /lib/ext JDK)
- Обычный: Application ClassLoader (классы в собственном пути к классам программы)
Первое вниманиеМеханизм родительского делегированияОчень важным моментом является то, что если класс может быть делегирован для загрузки с помощью самого простого ClassLoader, он не может быть загружен с помощью ClassLoader более высокого уровня, который должен ввести не-JDK-класс с тем же именем класса для неправильной области. Во-вторых, если в рамках этого механизма за счетfat jar
Различные третьи стороны, на которые полагались вjar
файл, а не в самой программеclasspath
down, то есть если мы используем механизм родительского делегирования,Мы не можем получить пакет jar, от которого мы вообще зависим, поэтому нам нужно изменить метод поиска классов в родительском механизме делегирования и настроить механизм загрузки классов..
Сначала краткое введение в Springboot2.LaunchedURLClassLoader
, класс наследуетjava.net.URLClassLoader
, переписаноjava.lang.ClassLoader#loadClass(java.lang.String, boolean)
, а затем мы обсуждаем, как он модифицирует механизм родительского делегирования.
Выше мы упоминали, что Spring boot поддерживает несколько '!/' для представления нескольких jar-файлов, и наша проблема заключается в том, как найти эти несколько jar-пакетов. Давайте взглянемLaunchedURLClassLoader
метод построения.
public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
urls
Примечания объясняютthe URLs from which to load classes and resources
, то есть все классы и ресурсы, от которых зависит пакет fat jar,urls
Параметры, переданные в родительский классjava.net.URLClassLoader
, по родительскому классуjava.net.URLClassLoader#findClass
Выполните метод класса поиска, источником поиска класса является параметр urls, переданный конструктором.
//LaunchedURLClassLoader的实现
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Handler.setUseFastConnectionExceptions(true);
try {
try {
//尝试根据类名去定义类所在的包,即java.lang.Package,确保jar in jar里匹配的manifest能够和关联 //的package关联起来
definePackageIfNecessary(name);
}
catch (IllegalArgumentException ex) {
// Tolerate race condition due to being parallel capable
if (getPackage(name) == null) {
// This should never happen as the IllegalArgumentException indicates
// that the package has already been defined and, therefore,
// getPackage(name) should not return null.
//这里异常表明,definePackageIfNecessary方法的作用实际上是预先过滤掉查找不到的包
throw new AssertionError("Package " + name + " has already been "
+ "defined but it could not be found");
}
}
return super.loadClass(name, resolve);
}
finally {
Handler.setUseFastConnectionExceptions(false);
}
}
методsuper.loadClass(name, resolve)
на самом деле вернетсяjava.lang.ClassLoader#loadClass(java.lang.String, boolean)
, следуйте механизму родительского делегирования для классов поиска иBootstrap ClassLoader
иExtension ClassLoader
не найдет классов, от которых зависит толстая банка, и в конце концов придет кApplication ClassLoader
,перечислитьjava.net.URLClassLoader#findClass
Как на самом деле начать
Самая большая разница между Springboot2 и Springboot1 заключается в том, что Springboo1 запускает новый поток для выполнения соответствующегологика вызова отражения, в то время как SpringBoot2 удаляет этап создания нового потока. путьorg.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader)
Логика размышлений вызывает относительно проста, поэтому мы не проанализируем ее здесь. Основная точка в том, что при вызовеmain
Перед методом настроен загрузчик класса контекста текущего потока.LaunchedURLClassLoader
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
Demo
public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {
JarFile.registerUrlProtocolHandler();
// 构造LaunchedURLClassLoader类加载器,这里使用了2个URL,分别对应jar包中依赖包spring-boot-loader和spring-boot,使用 "!/" 分开,需要org.springframework.boot.loader.jar.Handler处理器处理
LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(
new URL[] {
new URL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-loader-1.2.3.RELEASE.jar!/")
, new URL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-2.1.3.RELEASE.jar!/")
},
Application.class.getClassLoader());
// 加载类
// 这2个类都会在第二步本地查找中被找出(URLClassLoader的findClass方法)
classLoader.loadClass("org.springframework.boot.loader.JarLauncher");
classLoader.loadClass("org.springframework.boot.SpringApplication");
// 在第三步使用默认的加载顺序在ApplicationClassLoader中被找出
classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");
// SpringApplication.run(Application.class, args);
}
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-loader -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
Суммировать
Для анализа исходного кода самым большим преимуществом на этот раз является то, что я не могу сразу же понять логику каждого шага кода в исходном коде, даже если я знаю функцию метода. Что нам нужно понять, так это ключевой код и задействованные точки знаний. Я начал отслеживать с пользовательского плагина Maven и закрепил свои знания о Maven.В процессе я даже узнал, что JDK предоставляет соответствующие классы инструментов для чтения jar-файлов. Последний и самый важный пункт знаний — это пользовательский загрузчик классов. Весь код предназначен не для того, чтобы сказать, насколько он хорош, а для того, чтобы понять, почему он хорош.
Автор: пожалуйста, называйте меня красным шарфом
Источник:Наггетс.Личное сообщение/пост/5 из 15 из 4…
Этот блог приветствуется для перепечатки, но это заявление должно быть сохранено без согласия автора, а ссылка на исходный текст дана в явном месте на странице статьи, в противном случае сохраняется право привлечения к юридической ответственности. Кодировать слова непросто, ваши лайки — самая большая мотивация для моего письма.
Использованная литература:Анализ исполняемого файла SpringBoot анализа исходного кода SpringBoot