spring-boot-maven-plugin插件是將springboot的應(yīng)用程序打包成fat jar的插件极谊。首先我們說一下啥叫fat jar轻猖。fat jar 我們暫且叫他胖jar吧,實在是找不到官方叫法了咙边。我們一般的jar琉雳,里面放的是.class文件已經(jīng)resources目錄下的東西翠肘,但是fat jar 它可以把jar作為內(nèi)容包含進(jìn)去辫秧。也就是說盟戏,spring boot 借助spring-boot-maven-plugin將所有應(yīng)用啟動運行所需要的jar都包含進(jìn)來,從邏輯上將具備了獨立運行的條件邮旷。
我們將普通插件maven-jar-plugin生成的包和spring-boot-maven-plugin生成的包unzip,比較一下他們直接的區(qū)別婶肩,發(fā)現(xiàn)使用spring-boot-maven-plugin生成的jar中主要增加了兩部分律歼,第一部分是lib目錄险毁,這里存放的是應(yīng)用的Maven依賴的jar包文件,第二部分是spring boot loader相關(guān)的類畔况,這個我們下一節(jié)再說spring boot 的加載流程问窃。
在項目中需要先加入spring-boot-maven-plugin嵌戈。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.3.2.RELEASE</version>
<configuration>
<mainClass>test.ApplicationMain</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
默認(rèn)是在package階段執(zhí)行spring-boot-maven-plugin repackage這個目標(biāo)。我們看一下RepackageMojo的關(guān)鍵方法execute
@Override
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;
}
//得到項目中的原始的jar听皿,就是使用maven-jar-plugin生成的jar
File source = this.project.getArtifact().getFile();
//要寫入的目標(biāo)文件熟呛,就是fat jar
File target = getTargetFile();
Repackager repackager = new Repackager(source) {
//從source中尋找spring boot 應(yīng)用程序入口的main方法。
@Override
protected String findMainMethod(JarFile source) throws IOException {
long startTime = System.currentTimeMillis();
try {
return super.findMainMethod(source);
}
finally {
long duration = System.currentTimeMillis() - startTime;
if (duration > FIND_WARNING_TIMEOUT) {
getLog().warn("Searching for the main-class is taking some time, "
+ "consider using the mainClass configuration "
+ "parameter");
}
}
}
};
//如果插件中指定了mainClass就直接使用
repackager.setMainClass(this.mainClass);
if (this.layout != null) {
getLog().info("Layout: " + this.layout);
repackager.setLayout(this.layout.layout());
}
//尋找項目運行時依賴的jar尉姨,過濾后
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
getFilters(getAdditionalFilters()));
//將Artifact轉(zhuǎn)化成Libraries
Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
getLog());
try {
LaunchScript launchScript = getLaunchScript();
//進(jìn)行repackage
repackager.repackage(target, libraries, launchScript);
}
catch (IOException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
if (this.classifier != null) {
getLog().info("Attaching archive: " + target + ", with classifier: "
+ this.classifier);
this.projectHelper.attachArtifact(this.project, this.project.getPackaging(),
this.classifier, target);
}
else if (!source.equals(target)) {
this.project.getArtifact().setFile(target);
getLog().info("Replacing main artifact " + source + " to " + target);
}
}
基本上重要的步驟都有注釋庵朝,應(yīng)該不難理解的。再來看下面又厉,當(dāng)然也不是重點九府,看看就行。
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");
}
if (alreadyRepackaged()) {
return;
}
destination = destination.getAbsoluteFile();
File workingSource = this.source;
//如果源jar與目標(biāo)jar的文件路徑及名稱是一致的
if (this.source.equals(destination)) {
//將源jar重新命名為原名稱+.original,同時刪除原來的源jar
workingSource = new File(this.source.getParentFile(),
this.source.getName() + ".original");
workingSource.delete();
renameFile(this.source, workingSource);
}
destination.delete();
try {
//將源jar變成JarFile
JarFile jarFileSource = new JarFile(workingSource);
try {
repackage(jarFileSource, destination, libraries, launchScript);
}
finally {
jarFileSource.close();
}
}
finally {
if (!this.backupSource && !this.source.equals(workingSource)) {
deleteFile(workingSource);
}
}
}
這一步所做的是清理工作覆致,如果源jar同目標(biāo)文件路徑名稱等一致侄旬,將源jar重命名,原來的文件刪除。為目標(biāo)文件騰位置族操。下面的重點來了炕婶。
private void repackage(JarFile sourceJar, File destination, Libraries libraries,
LaunchScript launchScript) throws IOException {
JarWriter writer = new JarWriter(destination, launchScript);
try {
final List<Library> unpackLibraries = new ArrayList<Library>();
final List<Library> standardLibraries = new ArrayList<Library>();
libraries.doWithLibraries(new LibraryCallback() {
@Override
public void library(Library library) throws IOException {
File file = library.getFile();
if (isZip(file)) {
if (library.isUnpackRequired()) {
unpackLibraries.add(library);
}
else {
standardLibraries.add(library);
}
}
}
});
//按照規(guī)則寫入manifest文件
writer.writeManifest(buildManifest(sourceJar));
Set<String> seen = new HashSet<String>();
writeNestedLibraries(unpackLibraries, seen, writer);
//寫入源jar中的內(nèi)容
writer.writeEntries(sourceJar);
//寫入標(biāo)準(zhǔn)的jar,依賴的jar
writeNestedLibraries(standardLibraries, seen, writer);
if (this.layout.isExecutable()) {
//寫入spring boot loader的類
writer.writeLoaderClasses();
}
}
finally {
try {
writer.close();
}
catch (Exception ex) {
// Ignore
}
}
}
上面就是一通寫危喉,將所需要的內(nèi)容全部寫入到目標(biāo)文件中。然后就有了我們的fat jar。