咱們知道,SpringBoot僅憑一個Jar包就能將咱們構建的整個工程跑起來,若是你也想知道這個能跑起來的jar內部結構是如何構建出來的,請耐心讀完本篇,本篇內容可能有點多,但包你有收穫。若是讀完沒有收穫,請拉到文章最後,我再告訴你一個絕招。php
分析Springboot重構Jar包源碼前咱們先按日常方式建立一個springboot項目,經過IDEA或springboot提供的網站(https://start.spring.io/)很容易就能夠建立出一個springboot web工程。注意:建立時要把Web Starter選擇上,否則後面不會啓動Tomcat容器。建立好的項目目錄通常大體以下:java
pom.xml文件以下:git
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.sourcecode.analysis</groupId> <artifactId>sourcecode-analysis-springboot</artifactId> <version>0.0.1-SNAPSHOT</version> <name>sourcecode-analysis-springboot</name> <description>Demo project for Spring Boot</description>
<properties> <java.version>1.8</java.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
SourcecodeAnalysisSpringbootApplication.java類以下:github
package com.sourcecode.analysis.springboot;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;
public class SourcecodeAnalysisSpringbootApplication {
public static void main(String[] args) { SpringApplication.run(SourcecodeAnalysisSpringbootApplication.class, args); }
}
application.properties文件默認是空的。web
當springboot項目建立好以後,咱們能夠直接運行SourcecodeAnalysisSpringbootApplication.java類的main方法便可啓動web工程,啓動成功控制檯以下:(默認端口8080)spring
除此以外咱們也能夠經過執行maven package方式將項目打成jar包,經過java -jar 命令來運行,運行效果以下:typescript
springboot項目建立過程就是這麼多,接下來就能夠在這個腳手架基礎上去填咱們的業務代碼了。在springboot以前要實現這個過程但是要配置一大堆xml的,可是如今這些事情springboot經過starter的方式幫咱們作了。因爲咱們本章主要內容是講解springboot重構jar包的源碼,關於springboot starter如何幫咱們簡化配置的等到後面有時間再寫一篇來介紹。apache
當經過java -jar命令啓動jar包時,首先會先從jar包中META-INF/MANIFEST.MF文件中找到Main-Class的值做爲主類來運行jar包,這是java基礎知識。因此說要了解springboot是如何啓動的,咱們首先須要將springboot打出來的jar包解壓出來,找到META-INF/MANIFEST.MF文件並打開,咱們能夠看到大概以下內容:編程
Manifest-Version: 1.0Implementation-Title: sourcecode-analysis-springbootImplementation-Version: 0.0.1-SNAPSHOTStart-Class: com.sourcecode.analysis.springboot.SourcecodeAnalysisSpri ngbootApplicationSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Build-Jdk-Spec: 1.8Spring-Boot-Version: 2.2.2.RELEASECreated-By: Maven Archiver 3.4.0Main-Class: org.springframework.boot.loader.JarLauncher
能夠看到Main-Class的值爲: org.springframework.boot.loader.JarLauncher。經過java -jar運行時執行的主方法即是 org.springframework.boot.loader.JarLauncher類的main方法。而咱們經過IDEA手工運行的主類被配置爲key爲Start-Class的值中,這個類是怎麼寫到META-INF/MANIFEST.MF文件中的呢?由於咱們前面是經過maven package命令打包出來的,因此要解開這個問題咱們要回到maven打包階段去思考,想到這裏此時咱們應該看下pom.xml的插件配置,能夠看到以下核心配置:springboot
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration> </plugin> </plugins> </build>
從名字感官上能夠看出這應該是springboot打包插件,咱們能夠大膽猜想springboot經過本身實現的maven package階段插件對maven-jar-plugin插件建立的jar動了手腳。爲了找到具體線索,查看maven package命令日誌,能夠看到打包階段執行完maven-jar-plugin插件後接下來果真是執行了spring-boot-maven-plugin插件中的repackage進行jar重構。
爲了深刻了解springboot是如何對maven-jar-plugin插件原始jar包動手腳的,咱們把springboot源碼下載下來準備進行源碼分析,springboot github 源碼clone地址:https://github.com/spring-projects/spring-boot.git。克隆下來源碼目錄結構以下:
緊接着咱們順藤摸瓜,經過全局搜索repackag關鍵字的方式從springboot源碼中找到一個註解@Mojo爲repackage的RepackageMojo類文件:
這個類頂層繼承自maven插件抽象父類AbstractMojo,且被定義爲打包階段執行,這個類正是spring-boot-maven-plugin插件中執行的repackage,咱們找到插件默認執行的execute方法源碼以下:
public void execute() throws MojoExecutionException, MojoFailureException { if (this.project.getPackaging().equals("pom")) { getLog().debug("repackage goal could not be applied to pom project."); return; } if (this.skip) { getLog().debug("skipping repackaging as per configuration."); return; }//從新打包 repackage();}
咱們繼續跟入repackage()方法,源碼以下:
private void repackage() throws MojoExecutionException { // 獲取maven-jar-plugin插件構建的jar包 Artifact對象 Artifact source = getSourceArtifact(); // 獲取maven-jar-plugin插件構建的jar包 File對象 File target = getTargetFile(); // 實例化從新打包對象,整個打包工做基本由這個對象完成//將maven-jar-plugin插件構建的jar包 File對象傳入給source屬性 Repackager repackager = getRepackager(source.getFile()); // 過濾掉spring-boot-devtools依賴以及系統本地的依賴 // this.project.getArtifacts()返回//pom.xml當前階段範圍的全部依賴信息對象集合,包含傳遞過來的 Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters())); // 建立lib信息對象,這裏純粹傳參實例化沒有邏輯 Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog()); try { // 獲取sh應用管理腳本(start/stop/restart),可配置 // 這裏默認返回null,惟有配置插件時配置了executable //或embeddedLaunchScript時纔會實例化 LaunchScript launchScript = getLaunchScript(); // 進入核心重構jar方法 repackager.repackage(target, libraries, launchScript); } catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } //將重構好的File對象set到Artifact對象中,更新jar包文件 updateArtifact(source, target, repackager.getBackupFile());}
這個方法核心邏輯以下:
一、實例化maven-jar-plugin插件構建的原始jar包(後面統稱這個jar爲原始jar)文件對象
二、實例化啓動腳本LaunchScript對象,因爲沒有配置參數,因此這裏返回null
三、實例化Libraries依賴信息對象,這裏原理是讀取pom.xml依賴後轉爲Set<Artifact>集合
四、實例化重構jar包核心工做對象Repackager
五、調用Repackager對象的repackager重構jar方法,將實例化好的參數帶入
六、最後重構完成後更新原來Artifact File對象
咱們繼續跟入Repackager對象的repackage方法,源碼以下:
public void repackage(File destination, Libraries libraries, LaunchScript launchScript) throws IOException {
//參數校驗 if (destination == null || destination.isDirectory()) { throw new IllegalArgumentException("Invalid destination"); } if (libraries == null) { throw new IllegalArgumentException("Libraries must not be null"); }
//實例化layout屬性//layout做用是定義重構jar包內部目錄名稱//以及定義是否須要寫入classloader使jar文件可執行,默認true if (this.layout == null) { //這裏默認根據文件後綴判斷實例化哪一個佈局對象,//分別有Jar War Expanded(zip)等,默認實例化JarLayout對象 this.layout = getLayoutFactory().getLayout(this.source); }
// maven-jar-plugin構建的原始jar包File對象 destination = destination.getAbsoluteFile(); File workingSource = this.source; if (alreadyRepackaged() && this.source.equals(destination)) { return; } if (this.source.equals(destination)) {workingSource = getBackupFile(); workingSource.delete();// 將原始jar包備份(重命名)爲原始jar名稱.original文件 renameFile(this.source, workingSource); }//刪除原始jar文件 destination.delete(); try { //JarFile是jdk自帶的類,可從中獲取jar對象信息 try (JarFile jarFileSource = new JarFile(workingSource)) { //將備份jar做爲源,將刪除後的原始jar做新的目的地,//傳入lib對象、啓動腳本等參數進入repackage對象的重載方法 repackage(jarFileSource, destination, libraries, launchScript); } } finally {
if (!this.backupSource && !this.source.equals(workingSource)) { //目前版本源碼中,這裏永遠不會執行 由於backupSource寫死爲true deleteFile(workingSource); } }}
這個方法核心邏輯以下:
一、對傳入的參數作必要性判斷
二、根據原始jar包後綴判斷初始化layout對象,這裏初始化的是JarLayout,JarLayout主要佈局信息爲:BOOT-INF/lib爲lib目錄,BOOT-INF/classes爲class類文件目錄,須要寫入springboot自定義的classloader使jar可正常執行
三、將maven-jar-plugin插件構建的原始jar包備份後綴爲original文件中,並將原始jar包刪除,這麼作目的是準備好從備份original文件到原始jar包的重構環境
四、進入Repackager對象的repackage重載方法,將準備好的參數帶入
咱們繼續跟入Repackager對象的repackage重載方法,源碼以下:
private void repackage(JarFile sourceJar, File destination, Libraries libraries, LaunchScript launchScript) throws IOException {
// 遍歷前面傳入的artifacts//並封裝到內部Map<String, Library> libraryEntryNames中 // 過程當中將BOOT-INF/lib/目錄拼接上去,//例如put(「BOOT-INF/lib/aaa.jar」, Library) // 同時會對判斷是否存在重複依賴,存在會報錯Duplicate library aaa.jar WritableLibraries writeableLibraries = new WritableLibraries(libraries);
// JarWriter是springboot封裝了apache的JarArchiveOutputStream類 // 底層就是ziparchive版本的OutputStream輸出流,支持往jar寫東西的對象 // 這裏簡單理解成new FileOutputStream(new 前面被刪除的原始jar文件對象), // 意思是準備往前面被刪除的原始jar文件寫入東西(刪除後文件是乾淨的) try (JarWriter writer = new JarWriter(destination, launchScript)) {
// 寫入META-INF/MANIFEST.MF文件內容 // 咱們看到的META-INF/MANIFEST.MF文件內容所有都是這個方法寫入的 writer.writeManifest(buildManifest(sourceJar));
// 根據layout配置寫入SpringBoot實現的ClassLoader // 寫入classloader緣由是重構後的jar包的lib目錄爲BOOT-INF/lib, // 外部的classloader並不知道重構後的lib應該到哪裏加載,// 因此springboot插件須要實現一個classloader並設置到線程上下文// 給後續加載類時使用,這裏寫入的classloader是springboot// 項目的其中一個module,打成jar包後解壓寫入進去的 writeLoaderClasses(writer); if (this.layout instanceof RepackagingLayout) { //將備份的original中class文件和其餘資源文件所有寫入到原始jar的//BOOT-INF/classes/目錄下,寫入過程進行了SHA-1簽名 writer.writeEntries(sourceJar, new RenamingEntryTransformer(((RepackagingLayout) this.layout).getRepackagedClassesLocation()), writeableLibraries); } else { writer.writeEntries(sourceJar, writeableLibraries); } // 最後遍歷前面WritableLibraries對象內部// Map<String, Library> libraryEntryNames // 將全部lib寫入到原始jar中 writeableLibraries.write(writer); }}
這個方法從頂層直觀告訴咱們springboot對原始jar包動手腳的整個前後順序。核心邏輯以下:
一、實例化lib信息預寫對象,主要是遍歷libratis對象的artifacts屬性,封裝到map中
二、將原始jar File對象包裝成JarWrite對象,準備往乾淨的(由於前面被刪過)原始jar文件寫入新的東西
三、往原始jar寫入META-INF/MANIFEST.MF文件內容
四、往原始jar寫入classloader
五、從備份的original文件複製class類文件以及其餘資源文件寫入到原始jar BOOT-INF/classes目錄下
六、將從pom.xml解析到的artifacts lib依賴包文件流寫入到原始jar BOOT-INF/lib目錄下
爲了更加深度解析,咱們分別進入每一個寫入方法大體過一下處理邏輯源碼,下面邏輯都寫在每一行代碼註釋中,請用心看註釋描述。另外配套前面頂層源碼的截圖信息。
一、實例化lib信息預寫對象源碼以下:
頂層源碼截圖:
// Repackager類的一個內部類private final class WritableLibraries implements UnpackHandler {
private final Map<String, Library> libraryEntryNames = new LinkedHashMap<>();
//外部new調用的參數構造器 private WritableLibraries(Libraries libraries) throws IOException { // libraries實現類爲ArtifactsLibraries,前面分析代碼過程new出來的 // doWithLibraries方法代碼下面有截圖,邏輯很簡單就是遍歷artifact,而後 // 判斷去重後調用這裏的箭頭函數,最後工做就是保存到map中 libraries.doWithLibraries((library) -> { if (isZip(library.getFile())) { // 這裏layout代碼以下,寫死ruturn BOOT-INF/lib/ // public String getLibraryDestination(String libraryName, LibraryScope scope) { // return "BOOT-INF/lib/"; // } String libraryDestination = Repackager.this.layout.getLibraryDestination(library.getName(), library.getScope()); if (libraryDestination != null) {
// 放到map中,key=BOOT-INF/lib/ + jar名稱, value=library信息對象 // 這裏提一下,library裏面有個File file屬性,這個屬性纔是jar文件對象 Library existing = this.libraryEntryNames.putIfAbsent(libraryDestination + library.getName(), library); if (existing != null) { throw new IllegalStateException("Duplicate library " + library.getName()); } } } }); }
// 判斷是否容許打開文件,主要後面sha1Hash簽名時判斷用 public boolean requiresUnpack(String name) { Library library = this.libraryEntryNames.get(name); return library != null && library.isUnpackRequired(); }
// 文件加密方法 public String sha1Hash(String name) throws IOException { Library library = this.libraryEntryNames.get(name); if (library == null) { throw new IllegalArgumentException("No library found for entry name '" + name + "'"); } return FileUtils.sha1Hash(library.getFile()); }
//能夠看到寫入是經過外部傳入的JarWriter來執行的 //這個外部JarWriter實際就是原始jar的JarWriter對象 private void write(JarWriter writer) throws IOException { for (Entry<String, Library> entry : this.libraryEntryNames.entrySet()) { writer.writeNestedLibrary(entry.getKey().substring(0, entry.getKey().lastIndexOf('/') + 1), entry.getValue()); } }
}
ArtifactsLibraries對象的doWithLibraries方法源碼以下:
public void doWithLibraries(LibraryCallback callback) throws IOException { Set<String> duplicates = getDuplicates(this.artifacts); for (Artifact artifact : this.artifacts) { LibraryScope scope = SCOPES.get(artifact.getScope()); if (scope != null && artifact.getFile() != null) { String name = getFileName(artifact); // 處理重複jar名稱 if (duplicates.contains(name)) { this.log.debug("Duplicate found: " + name); name = artifact.getGroupId() + "-" + name; this.log.debug("Renamed to: " + name); } // 回調傳入的函數,artifact.getFile()是重點, // 它是真正jar的File 對象,能夠讀出來寫入到其它地方 callback.library(new Library(name, artifact.getFile(), scope, isUnpackRequired(artifact))); } }}
2、將原始jar File對象包裝成JarWrite對象源碼以下:
頂層源碼截圖:
public JarWriter(File file, LaunchScript launchScript) throws FileNotFoundException, IOException { FileOutputStream fileOutputStream = new FileOutputStream(file); if (launchScript != null) { // 若是存在腳本,直接寫入原始jar文件 fileOutputStream.write(launchScript.toByteArray()); // 修改下文件執行權限 setExecutableFilePermission(file); } this.jarOutput = new JarArchiveOutputStream(fileOutputStream); this.jarOutput.setEncoding("UTF-8");}
三、寫入META-INF/MANIFE文件內容源碼以下:
頂層源碼截圖:
buildManifest源碼以下:
private Manifest buildManifest(JarFile source) throws IOException { // 備份original文件的Manifest文件 Manifest manifest = source.getManifest(); if (manifest == null) { manifest = new Manifest(); manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); } // 從新實例化一個新的Manifest文件 manifest = new Manifest(manifest); String startClass = this.mainClass; if (startClass == null) { startClass = manifest.getMainAttributes().getValue(MAIN_CLASS_ATTRIBUTE); } if (startClass == null) { //這裏根據規則去找Application啓動類,也就是Start-Class的值 //IDEA咱們手工run的那個主類 //若是查找超過必定時間會發出警告,建議讓你手工配置,查找比較耗時 startClass = findMainMethodWithTimeoutWarning(source); } //layout。getLauncherClassName方法以下: // public String getLauncherClassName() { // return "org.springframework.boot.loader.JarLauncher"; // } String launcherClassName = this.layout.getLauncherClassName(); if (launcherClassName != null) { // 寫入 Main-Class manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, launcherClassName); if (startClass == null) { throw new IllegalStateException("Unable to find main class"); } // 寫入 Start-Class manifest.getMainAttributes().putValue(START_CLASS_ATTRIBUTE, startClass); } else if (startClass != null) { manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, startClass); } String bootVersion = getClass().getPackage().getImplementationVersion(); manifest.getMainAttributes().putValue(BOOT_VERSION_ATTRIBUTE, bootVersion); // 寫入Spring-Boot-Classes = BOOT-INF/classes/ manifest.getMainAttributes().putValue(BOOT_CLASSES_ATTRIBUTE, (this.layout instanceof RepackagingLayout) ? ((RepackagingLayout) this.layout).getRepackagedClassesLocation() : this.layout.getClassesLocation()); String lib = this.layout.getLibraryDestination("", LibraryScope.COMPILE); if (StringUtils.hasLength(lib)) { // 寫入Spring-Boot-Lib = BOOT-INF/lib/ manifest.getMainAttributes().putValue(BOOT_LIB_ATTRIBUTE, lib); } return manifest;}
writeManifest關鍵源碼以下:
public void writeManifest(Manifest manifest) throws IOException { JarArchiveEntry entry = new JarArchiveEntry("META-INF/MANIFEST.MF"); writeEntry(entry, manifest::write);}
writeEntry有多個重載方法,最終都走的是下面的重載方法:
private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter, UnpackHandler unpackHandler) throws IOException {
String parent = entry.getName(); if (parent.endsWith("/")) { parent = parent.substring(0, parent.length() - 1); entry.setUnixMode(UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM); } else { entry.setUnixMode(UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM); } if (parent.lastIndexOf('/') != -1) { parent = parent.substring(0, parent.lastIndexOf('/') + 1); if (!parent.isEmpty()) { writeEntry(new JarArchiveEntry(parent), null, unpackHandler); } }
if (this.writtenEntries.add(entry.getName())) { // 這裏判斷是否容許打開文件,若是容許則往裏面寫入sha1Hash簽名 entryWriter = addUnpackCommentIfNecessary(entry, entryWriter, unpackHandler); //前面實例化的原始jar的JarArchiveOutputStream對象 //這裏表明寫入entry到輸出流內存中 this.jarOutput.putArchiveEntry(entry); if (entryWriter != null) { //將輸出流寫到entryWriter對象 entryWriter.write(this.jarOutput); } //關閉資源 this.jarOutput.closeArchiveEntry(); }}
四、寫入classloader源碼以下:
頂層源碼截圖:
writeLoaderClasses源碼以下:
private void writeLoaderClasses(JarWriter writer) throws IOException { // 自定義佈局時才走這裏,通常不多人這麼閒本身實現calssloader的 if (this.layout instanceof CustomLoaderLayout) { ((CustomLoaderLayout) this.layout).writeLoadedClasses(writer); } // layout.isExecutable固定返回true,全部進入這裏 else if (this.layout.isExecutable()) { writer.writeLoaderClasses(); }}
連環調用源碼以下:
private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar";
public void writeLoaderClasses() throws IOException { writeLoaderClasses(NESTED_LOADER_JAR);}
public void writeLoaderClasses(String loaderJarResourceName) throws IOException { // getClass().getClassLoader()返回的是JDK的sun.misc.Launcher$AppClassLoader類加載器 // 獲取META-INF/loader/spring-boot-loader.jar文件 URL loaderJar = getClass().getClassLoader().getResource(loaderJarResourceName); try (JarInputStream inputStream = new JarInputStream(new BufferedInputStream(loaderJar.openStream()))) { JarEntry entry; while ((entry = inputStream.getNextJarEntry()) != null) { if (entry.getName().endsWith(".class")) { // 遍歷spring-boot-loader.jar文件 //把全部的.class文件寫入到原始jar包中 writeEntry(new JarArchiveEntry(entry), new InputStreamEntryWriter(inputStream)); } } }}
五、從備份original文件複製class文件源碼以下:
頂層源碼截圖:
void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler) throws IOException { Enumeration<JarEntry> entries = jarFile.entries(); //遍歷整個備份的original文件 while (entries.hasMoreElements()) { JarArchiveEntry entry = new JarArchiveEntry(entries.nextElement()); setUpEntry(jarFile, entry); try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) { EntryWriter entryWriter = new InputStreamEntryWriter(inputStream); JarArchiveEntry transformedEntry = entryTransformer.transform(entry); if (transformedEntry != null) { //將全部class類文件以及其餘資源文件寫入到原始jar中 //這裏走的是前面分析的帶簽名的writeEntry重裝方法 writeEntry(transformedEntry, entryWriter, unpackHandler); } } }}
void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler) throws IOException { Enumeration<JarEntry> entries = jarFile.entries(); //遍歷整個備份的original文件 while (entries.hasMoreElements()) { JarArchiveEntry entry = new JarArchiveEntry(entries.nextElement()); setUpEntry(jarFile, entry); try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) { EntryWriter entryWriter = new InputStreamEntryWriter(inputStream); JarArchiveEntry transformedEntry = entryTransformer.transform(entry); if (transformedEntry != null) { //將全部class類文件以及其餘資源文件寫入到原始jar中 //這裏走的是前面分析的帶簽名的writeEntry重裝方法 writeEntry(transformedEntry, entryWriter, unpackHandler); } } }}
六、寫入lib資源源碼以下:
頂層源碼截圖:
//能夠看到寫入是經過外部傳入的JarWriter來執行的//這個外部JarWriter實際就是原始jar的JarWriter對象private void write(JarWriter writer) throws IOException { for (Entry<String, Library> entry : this.libraryEntryNames.entrySet()) { writer.writeNestedLibrary(entry.getKey().substring(0, entry.getKey().lastIndexOf('/') + 1), entry.getValue()); }}
整個springboot重構jar包源碼分析到此就算結束了,你get到了嗎?
若是你讀完還一頭霧水沒啥收穫,這裏給出的絕招是:請集中精力,再讀一遍!
長按掃碼關注《Java軟件編程之家》微信公衆號
本文分享自微信公衆號 - Java軟件編程之家(gh_b3a87885f8f5)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。