SpringBoot java-jar命令行啟動(dòng)原理解析
在spring boot里,很吸引人的一個(gè)特性是可以直接把應(yīng)用打包成為一個(gè)jar/war,然后這個(gè)jar/war是可以直接啟動(dòng)的,而不需要另外配置一個(gè)Web Server。那么spring boot如何啟動(dòng)的呢?今天我們就來(lái)一起探究一下它的原理。首先我們來(lái)創(chuàng)建一個(gè)基本的spring boot工程來(lái)幫助我們分析,本次spring boot版本為 2.2.5.RELEASE。
// SpringBootDemo.java@SpringBootApplicationpublic class SpringBootDemo { public static void main(String[] args) { SpringApplication.run(SpringBootDemo.class); }}
下面是pom依賴(lài):
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency></dependencies><build> <finalName>springboot-demo</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>
創(chuàng)建完工程后,執(zhí)行maven的打包命令,會(huì)生成兩個(gè)jar文件:
springboot-demo.jarspringboot-demo.jar.original
其中springboot-demo.jar.original是默認(rèn)的maven-jar-plugin生成的包。springboot-demo.jar是spring boot maven插件生成的jar包,里面包含了應(yīng)用的依賴(lài),以及spring boot相關(guān)的類(lèi)。下面稱(chēng)之為executable jar或者fat jar。后者僅包含應(yīng)用編譯后的本地資源,而前者引入了相關(guān)的第三方依賴(lài),這點(diǎn)從文件大小也能看出。
圖1
關(guān)于executable jar,spring boot官方文檔中是這樣解釋的。
Executable jars (sometimes called “fat jars”) are archives containing your compiled classes along with all of the jar dependencies that your code needs to run.
Executable jar(有時(shí)稱(chēng)為“fat jars”)是包含您的已編譯類(lèi)以及代碼需要運(yùn)行的所有jar依賴(lài)項(xiàng)的歸檔文件。
Java does not provide any standard way to load nested jar files (that is, jar files that are themselves contained within a jar). This can be problematic if you need to distribute a self-contained application that can be run from the command line without unpacking.
Java沒(méi)有提供任何標(biāo)準(zhǔn)的方式來(lái)加載嵌套的jar文件(即,它們本身包含在jar中的jar文件)。如果您需要分發(fā)一個(gè)自包含的應(yīng)用程序,而該應(yīng)用程序可以從命令行運(yùn)行而無(wú)需解壓縮,則可能會(huì)出現(xiàn)問(wèn)題。
To solve this problem, many developers use “shaded” jars. A shaded jar packages all classes, from all jars, into a single “uber jar”. The problem with shaded jars is that it becomes hard to see which libraries are actually in your application. It can also be problematic if the same filename is used (but with different content) in multiple jars.
為了解決這個(gè)問(wèn)題,許多開(kāi)發(fā)人員使用 shaded jars。 一個(gè) shaded jar 將來(lái)自所有jar的所有類(lèi)打包到一個(gè) uber(超級(jí))jar 中。 shaded jars的問(wèn)題在于,很難查看應(yīng)用程序中實(shí)際包含哪些庫(kù)。 如果在多個(gè)jar中使用相同的文件名(但具有不同的內(nèi)容),也可能會(huì)產(chǎn)生問(wèn)題。
Spring Boot takes a different approach and lets you actually nest jars directly.
Spring Boot采用了另一種方法,實(shí)際上允許您直接嵌套jar。
簡(jiǎn)單來(lái)說(shuō),Java標(biāo)準(zhǔn)中是沒(méi)有來(lái)加載嵌套的jar文件,就是jar中的jar的方式的,為了解決這一問(wèn)題,很多開(kāi)發(fā)人員采用shaded jars,但是這種方式會(huì)有一些問(wèn)題,而spring boot采用了不同于shaded jars的另一種方式。
Executable Jar 文件結(jié)構(gòu)
那么spring boot具體是如何實(shí)現(xiàn)的呢?帶著這個(gè)疑問(wèn),先來(lái)查看spring boot打好的包的目錄結(jié)構(gòu)(不重要的省略掉):
圖6
可以發(fā)現(xiàn),文件目錄遵循了下面的規(guī)范:
Application classes should be placed in a nestedBOOT-INF/classesdirectory. Dependencies should be placed in a nested BOOT-INF/libdirectory.
應(yīng)用程序類(lèi)應(yīng)該放在嵌套的BOOT-INF/classes目錄中。依賴(lài)項(xiàng)應(yīng)該放在嵌套的BOOT-INF/lib目錄中。
我們通常在服務(wù)器中使用java -jar命令啟動(dòng)我們的應(yīng)用程序,在Java官方文檔是這樣描述的:
Executes a program encapsulated in a JAR file. The filename argument is the name of a JAR file with a manifest that contains a line in the form Main-Class:classname that defines the class with the public static void main(String[] args) method that serves as your application’s starting point.
執(zhí)行封裝在JAR文件中的程序。filename參數(shù)是具有清單的JAR文件的名稱(chēng),該清單包含Main-Class:classname形式的行,該行使用公共靜態(tài)void main(String [] args)方法定義該類(lèi),該方法充當(dāng)應(yīng)用程序的起點(diǎn)。
When you use the -jar option, the specified JAR file is the source of all user classes, and other class path settings are ignored.
使用-jar選項(xiàng)時(shí),指定的JAR文件是所有用戶(hù)類(lèi)的源,而其他類(lèi)路徑設(shè)置將被忽略。
簡(jiǎn)單說(shuō)就是,java -jar 命令引導(dǎo)的具體啟動(dòng)類(lèi)必須配置在清單文件 MANIFEST.MF 的 Main-Class 屬性中,該命令用來(lái)引導(dǎo)標(biāo)準(zhǔn)可執(zhí)行的jar文件,讀取的是 MANIFEST.MF文件的Main-Class 屬性值,Main-Class 也就是定義包含了main方法的類(lèi)代表了應(yīng)用程序執(zhí)行入口類(lèi)。
那么回過(guò)頭再去看下之前打包好、解壓之后的文件目錄,找到 /META-INF/MANIFEST.MF 文件,看下元數(shù)據(jù):
Manifest-Version: 1.0 Implementation-Title: spring-boot-demo Implementation-Version: 1.0-SNAPSHOT Start-Class: com.example.spring.boot.demo.SpringBootDemo Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.2.5.RELEASE Created-By: Maven Archiver 3.4.0 Main-Class: org.springframework.boot.loader.JarLauncher
可以看到Main-Class是org.springframework.boot.loader.JarLauncher,說(shuō)明項(xiàng)目的啟動(dòng)入口并不是我們自己定義的啟動(dòng)類(lèi),而是JarLauncher。而我們自己的項(xiàng)目引導(dǎo)類(lèi)com.example.spring.boot.demo.SpringBootDemo,定義在了Start-Class屬性中,這個(gè)屬性并不是Java標(biāo)準(zhǔn)的MANIFEST.MF文件屬性。
spring-boot-maven-plugin 打包過(guò)程
我們并沒(méi)有添加org.springframework.boot.loader下的這些類(lèi)的依賴(lài),那么它們是如何被打包在 FatJar 里面的呢?這就必須要提到spring-boot-maven-plugin插件的工作機(jī)制了 。對(duì)于每個(gè)新建的 spring boot工程,可以在其 pom.xml 文件中看到如下插件:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>
這個(gè)是 SpringBoot 官方提供的用于打包 FatJar 的插件,org.springframework.boot.loader 下的類(lèi)其實(shí)就是通過(guò)這個(gè)插件打進(jìn)去的;
當(dāng)我們執(zhí)行package命令的時(shí)候會(huì)看到下面這樣的日志:
[INFO] --- spring-boot-maven-plugin:2.2.5.RELEASE:repackage (repackage) @ spring-boot-demo ---[INFO] Replacing main artifact with repackaged archive
repackage目標(biāo)對(duì)應(yīng)的將執(zhí)行到org.springframework.boot.maven.RepackageMojo#execute,該方法的主要邏輯是調(diào)用了org.springframework.boot.maven.RepackageMojo#repackage
// RepackageMojo.javaprivate void repackage() throws MojoExecutionException { // 獲取使用maven-jar-plugin生成的jar,最終的命名將加上.orignal后綴 Artifact source = getSourceArtifact(); // 最終文件,即Fat jar File target = getTargetFile(); // 獲取重新打包器,將重新打包成可執(zhí)行jar文件 Repackager repackager = getRepackager(source.getFile()); // 查找并過(guò)濾項(xiàng)目運(yùn)行時(shí)依賴(lài)的jar Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters())); // 將artifacts轉(zhuǎn)換成libraries Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog()); try { // 提供Spring Boot啟動(dòng)腳本 LaunchScript launchScript = getLaunchScript(); // 執(zhí)行重新打包邏輯,生成最后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());}// 繼續(xù)跟蹤getRepackager這個(gè)方法,知道Repackager是如何生成的,也就大致能夠推測(cè)出內(nèi)在的打包邏輯。private Repackager getRepackager(File source) { Repackager repackager = new Repackager(source, this.layoutFactory); repackager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener()); // 設(shè)置main class的名稱(chēng),如果不指定的話(huà)則會(huì)查找第一個(gè)包含main方法的類(lèi), // repacke最后將會(huì)設(shè)置org.springframework.boot.loader.JarLauncher repackager.setMainClass(this.mainClass); if (this.layout != null) { getLog().info('Layout: ' + this.layout); repackager.setLayout(this.layout.layout()); } return repackager;}
repackager設(shè)置了 layout方法的返回對(duì)象,也就是org.springframework.boot.loader.tools.Layouts.Jar
/** * 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我們可以將之翻譯為文件布局,或者目錄布局,代碼一看清晰明了,同時(shí)我們又發(fā)現(xiàn)了定義在MANIFEST.MF 文件的Main-Class屬性org.springframework.boot.loader.JarLauncher了,看來(lái)我們的下面的重點(diǎn)就是研究一下這個(gè)JarLauncher了。
JarLauncher構(gòu)造過(guò)程
因?yàn)閛rg.springframework.boot.loader.JarLauncher的類(lèi)是在spring-boot-loader中的,關(guān)于spring-boot-loader,spring boot的github上是這樣介紹的:
Spring Boot Loader provides the secret sauce that allows you to build a single jar file that can be launched usingjava -jar. Generally you will not need to use spring-boot-loaderdirectly, but instead work with the Gradle or Maven plugin.
Spring Boot Loader提供了秘密工具,可讓您構(gòu)建可以使用java -jar啟動(dòng)的單個(gè)jar文件。通常,您不需要直接使用spring-boot-loader,而可以使用Gradle或Maven插件。
但是若想在IDEA中來(lái)看源碼,需要在pom文件中引入如下配置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-loader</artifactId> <scope>provided</scope></dependency>
找到org.springframework.boot.loader.JarLauncher類(lèi)
// JarLauncher.javapublic class JarLauncher extends ExecutableArchiveLauncher { // BOOT-INF/classes/ static final String BOOT_INF_CLASSES = 'BOOT-INF/classes/'; // BOOT-INF/lib/ static final String BOOT_INF_LIB = 'BOOT-INF/lib/'; public JarLauncher() { } protected JarLauncher(Archive archive) { super(archive); } @Override protected boolean isNestedArchive(Archive.Entry entry) { if (entry.isDirectory()) { return entry.getName().equals(BOOT_INF_CLASSES); } return entry.getName().startsWith(BOOT_INF_LIB); } // main方法 public static void main(String[] args) throws Exception { new JarLauncher().launch(args); }}
可以發(fā)現(xiàn),JarLauncher定義了BOOT_INF_CLASSES和BOOT_INF_LIB兩個(gè)常量,正好就是前面我們解壓之后的兩個(gè)文件目錄。JarLauncher包含了一個(gè)main方法,作為應(yīng)用的啟動(dòng)入口。
從 main 來(lái)看,只是構(gòu)造了一個(gè) JarLauncher對(duì)象,然后執(zhí)行其 launch 方法 。再來(lái)看一下JarLauncher的繼承結(jié)構(gòu):
圖2
構(gòu)造JarLauncherd對(duì)象時(shí)會(huì)調(diào)用父類(lèi)ExecutableArchiveLauncher的構(gòu)造方法:
// ExecutableArchiveLauncher.javapublic ExecutableArchiveLauncher() { try { // 構(gòu)造 archive 對(duì)象 this.archive = createArchive(); } catch (Exception ex) { throw new IllegalStateException(ex); }}// 構(gòu)造 archive 對(duì)象protected final Archive createArchive() throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null; // 這里就是拿到當(dāng)前的 classpath 的絕對(duì)路徑 String path = (location != null) ? location.getSchemeSpecificPart() : null; if (path == null) { throw new IllegalStateException('Unable to determine code source archive'); } File root = new File(path); if (!root.exists()) { throw new IllegalStateException('Unable to determine code source archive from ' + root); } // 將構(gòu)造的archive 對(duì)象返回 return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));}
Archive
這里又需要我們先來(lái)了解一下Archive相關(guān)的概念。
archive即歸檔文件,這個(gè)概念在linux下比較常見(jiàn) 通常就是一個(gè)tar/zip格式的壓縮包 jar是zip格式public abstract class Archive { public abstract URL getUrl(); public String getMainClass(); public abstract Collection<Entry> getEntries(); public abstract List<Archive> getNestedArchives(EntryFilter filter);}
Archive是在spring boot里抽象出來(lái)的用來(lái)統(tǒng)一訪(fǎng)問(wèn)資源的接口。該接口有兩個(gè)實(shí)現(xiàn),分別是ExplodedArchive和JarFileArchive。前者是一個(gè)文件目錄,后者是一個(gè)jar,都是用來(lái)在文件目錄和jar中尋找資源的,這里看到JarLauncher既支持jar啟動(dòng),也支持文件系統(tǒng)啟動(dòng),實(shí)際上我們?cè)诮鈮汉蟮奈募夸浝飯?zhí)行 java org.springframework.boot.loader.JarLauncher 命令也是可以正常啟動(dòng)的。
圖3
在FatJar中,使用的是后者。Archive都有一個(gè)自己的URL,比如
jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!
Archive類(lèi)還有一個(gè)getNestedArchives方法,下面還會(huì)用到這個(gè)方法,這個(gè)方法實(shí)際返回的是springboot-demo.jar/lib下面的jar的Archive列表。它們的URL是:
jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!/BOOT-INF/lib/spring-boot-starter-web-2.2.5.RELEASE.jar!
jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!/BOOT-INF/lib/spring-boot-starter-2.2.5.RELEASE.jar!
jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!/BOOT-INF/lib/spring-boot-2.2.5.RELEASE.jar!
jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!/BOOT-INF/lib/spring-boot-autoconfigure-2.2.5.RELEASE.jar!/
省略......
launch()執(zhí)行流程
archive構(gòu)造完成后就該執(zhí)行JarLauncher的launch方法了,這個(gè)方法定義在了父類(lèi)的Launcher里:
// Launcher.javaprotected void launch(String[] args) throws Exception { /* * 利用 java.net.URLStreamHandler 的擴(kuò)展機(jī)制注冊(cè)了SpringBoot的自定義的可以解析嵌套jar的協(xié)議。 * 因?yàn)镾pringBoot FatJar除包含傳統(tǒng)Java Jar中的資源外還包含依賴(lài)的第三方Jar文件 * 當(dāng)SpringBoot FatJar被java -jar命令引導(dǎo)時(shí),其內(nèi)部的Jar文件是無(wú)法被JDK的默認(rèn)實(shí)現(xiàn) * sun.net.www.protocol.jar.Handler當(dāng)做classpath的,這就是SpringBoot的自定義協(xié)議的原因。 */ JarFile.registerUrlProtocolHandler(); // 通過(guò) classpath 來(lái)構(gòu)建一個(gè) ClassLoader ClassLoader classLoader = createClassLoader(getClassPathArchives()); // 1 launch(args, getMainClass(), classLoader); // 2}
重點(diǎn)關(guān)注下createClassLoader(getClassPathArchives()) 構(gòu)建ClassLoader的邏輯,首先調(diào)用getClassPathArchives()方法返回值作為參數(shù),該方法為抽象方法,具體實(shí)現(xiàn)在子類(lèi)ExecutableArchiveLauncher中:
// ExecutableArchiveLauncher.java@Overrideprotected List<Archive> getClassPathArchives() throws Exception { List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive)); postProcessClassPathArchives(archives); return archives;}
該方法會(huì)執(zhí)行Archive接口定義的getNestedArchives方法返回的與指定過(guò)濾器匹配的條目的嵌套存檔列表。從上文可以發(fā)現(xiàn),這里的archive其實(shí)就是JarFileArchive ,傳入的過(guò)濾器是JarLauncher#isNestedArchive方法引用
// JarLauncher.java@Overrideprotected boolean isNestedArchive(Archive.Entry entry) { // entry是文件目錄時(shí),必須是我們自己的業(yè)務(wù)類(lèi)所在的目錄 BOOT-INF/classes/ if (entry.isDirectory()) { return entry.getName().equals(BOOT_INF_CLASSES); } // entry是Jar文件時(shí),需要在依賴(lài)的文件目錄 BOOT-INF/lib/下面 return entry.getName().startsWith(BOOT_INF_LIB);}
getClassPathArchives方法通過(guò)過(guò)濾器將BOOT-INF/classes/和BOOT-INF/lib/下的嵌套存檔作為L(zhǎng)ist<Archive>返回參數(shù)傳入createClassLoader方法中。
// Launcher.javaprotected ClassLoader createClassLoader(List<Archive> archives) throws Exception { List<URL> urls = new ArrayList<>(archives.size()); for (Archive archive : archives) { // 前面說(shuō)到,archive有一個(gè)自己的URL的,獲得archive的URL放到list中 urls.add(archive.getUrl()); } // 調(diào)用下面的重載方法 return createClassLoader(urls.toArray(new URL[0]));}// Launcher.javaprotected ClassLoader createClassLoader(URL[] urls) throws Exception { return new LaunchedURLClassLoader(urls, getClass().getClassLoader());}
createClassLoader()方法目的是為得到的URL們創(chuàng)建一個(gè)類(lèi)加載器 LaunchedURLClassLoader,構(gòu)造時(shí)傳入了當(dāng)前Launcher的類(lèi)加載器作為其父加載器,通常是系統(tǒng)類(lèi)加載器。下面重點(diǎn)看一下LaunchedURLClassLoader的構(gòu)造過(guò)程:
// LaunchedURLClassLoader.javapublic LaunchedURLClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent);}
LaunchedURLClassLoader是spring boot自己定義的類(lèi)加載器,繼承了JDK的URLClassLoader并重寫(xiě)了loadClass方法,也就是說(shuō)它修改了默認(rèn)的類(lèi)加載方式,定義了自己的類(lèi)加載規(guī)則,可以從前面得到的 List<Archive>中加載依賴(lài)包的class文件了 。
LaunchedURLClassLoader創(chuàng)建完成后,我們回到Launcher中,下一步就是執(zhí)行l(wèi)aunch的重載方法了。
// Launcher.javalaunch(args, getMainClass(), classLoader);
在此之前,會(huì)調(diào)用getMainClass方法并將其返回值作為參數(shù)。
getMainClass的實(shí)現(xiàn)在Launcher的子類(lèi)ExecutableArchiveLauncher中:
// ExecutableArchiveLauncher.java@Overrideprotected String getMainClass() throws Exception { // 從 archive 中拿到 Manifest文件 Manifest manifest = this.archive.getManifest(); String mainClass = null; if (manifest != null) { // 就是MANIFEST.MF 文件中定義的Start-Class屬性,也就是我們自己寫(xiě)的com.example.spring.boot.demo.SpringBootDemo這個(gè)類(lèi) mainClass = manifest.getMainAttributes().getValue('Start-Class'); } if (mainClass == null) { throw new IllegalStateException('No ’Start-Class’ manifest entry specified in ' + this); } // 返回mainClass return mainClass;}
得到mainClass后,執(zhí)行l(wèi)aunch的重載方法:
// Launcher.javaprotected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { // 將自定義的LaunchedURLClassLoader設(shè)置為當(dāng)前線(xiàn)程上下文類(lèi)加載器 Thread.currentThread().setContextClassLoader(classLoader); // 構(gòu)建一個(gè) MainMethodRunner 實(shí)例對(duì)象來(lái)啟動(dòng)應(yīng)用 createMainMethodRunner(mainClass, args, classLoader).run();}// Launcher.javaprotected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) { return new MainMethodRunner(mainClass, args);}
MainMethodRunner對(duì)象構(gòu)建完成后,調(diào)用它的run方法:
// MainMethodRunner.javapublic void run() throws Exception { // 使用當(dāng)前線(xiàn)程上下文類(lèi)加載器也就是自定義的LaunchedURLClassLoader來(lái)加載我們自己寫(xiě)的com.example.spring.boot.demo.SpringBootDemo這個(gè)類(lèi) Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); // 找到SpringBootDemo的main方法 Method mainMethod = mainClass.getDeclaredMethod('main', String[].class); // 最后,通過(guò)反射的方式調(diào)用main方法 mainMethod.invoke(null, new Object[] { this.args });}
至此,我們自己的main方法開(kāi)始被調(diào)用,所有我們自己的應(yīng)用程序類(lèi)文件均可通過(guò)/BOOT-INF/classes加載,所有依賴(lài)的第三方j(luò)ar均可通過(guò)/BOOT-INF/lib加載,然后就開(kāi)始了spring boot的啟動(dòng)流程了。
debug技巧
以上就是spring boot通過(guò)java -jar命令啟動(dòng)的原理了,了解了原理以后我們可不可以通過(guò)debug來(lái)進(jìn)一步加深一下理解呢?通常我們?cè)贗DEA里啟動(dòng)時(shí)是直接運(yùn)行main方法,因?yàn)橐蕾?lài)的Jar都讓IDEA放到classpath里了,所以spring boot直接啟動(dòng)就完事了,并不會(huì)通過(guò)上面的方式來(lái)啟動(dòng)。不過(guò)我們可以通過(guò)配置IDEA的 run/debug configurations 配置 JAR Application 來(lái)實(shí)現(xiàn)通過(guò)Jar方式啟動(dòng)。
圖4
當(dāng)我們做了以上設(shè)置后,就可以來(lái)方便的在IDEA里來(lái)dubug源碼了。
圖5小結(jié)
本文通過(guò)JarLauncher為切入點(diǎn),介紹了spring boot的java -jar的啟動(dòng)方式,闡述了JarLauncher啟動(dòng)的基本工作原理,同時(shí)簡(jiǎn)單介紹了相關(guān)的spring-boot-maven-plugin插件和Archive、LaunchedURLClassLoader等相關(guān)概念,希望能夠?qū)Υ蠹业睦斫庥兴鶐椭?/p>
到此這篇關(guān)于SpringBoot java-jar命令行啟動(dòng)原理解析的文章就介紹到這了,更多相關(guān)SpringBoot java-jar命令行啟動(dòng)內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. PHP正則表達(dá)式函數(shù)preg_replace用法實(shí)例分析2. 一個(gè) 2 年 Android 開(kāi)發(fā)者的 18 條忠告3. vue使用moment如何將時(shí)間戳轉(zhuǎn)為標(biāo)準(zhǔn)日期時(shí)間格式4. js select支持手動(dòng)輸入功能實(shí)現(xiàn)代碼5. Android 實(shí)現(xiàn)徹底退出自己APP 并殺掉所有相關(guān)的進(jìn)程6. Android studio 解決logcat無(wú)過(guò)濾工具欄的操作7. 什么是Python變量作用域8. vue-drag-chart 拖動(dòng)/縮放圖表組件的實(shí)例代碼9. Spring的異常重試框架Spring Retry簡(jiǎn)單配置操作10. Vue實(shí)現(xiàn)仿iPhone懸浮球的示例代碼
