為什么SpringBoot的 jar 可以直接運(yùn)行技健?
SpringBoot提供了一個(gè)插件spring-boot-maven-plugin用于把程序打包成一個(gè)可執(zhí)行的jar包写穴。在pom文件里加入這個(gè)插件即可:
<build>????<plugins>????????<plugin>????????????<groupId>org.springframework.boot</groupId>????????????<artifactId>spring-boot-maven-plugin</artifactId>????????</plugin>????</plugins></build>
打包完生成的executable-jar-1.0-SNAPSHOT.jar內(nèi)部的結(jié)構(gòu)如下:
├── META-INF│ ??├── MANIFEST.MF│ ??└── maven│ ??????└── spring.study│ ??????????└── executable-jar│ ??????????????├── pom.properties│ ??????????????└── pom.xml├── lib│ ??├── aopalliance-1.0.jar│ ??├── classmate-1.1.0.jar│ ??├── spring-boot-1.3.5.RELEASE.jar│ ??├── spring-boot-autoconfigure-1.3.5.RELEASE.jar│ ??├── ...├── org│ ??└── springframework│ ??????└── boot│ ??????????└── loader│ ??????????????├── ExecutableArchiveLauncher$1.class│ ??????????????├── ...└── spring????└── study????????└── executablejar????????????└── ExecutableJarApplication.class
然后可以直接執(zhí)行jar包就能啟動程序了:
java -jar executable-jar-1.0-SNAPSHOT.jar
打包出來fat jar內(nèi)部有4種文件類型:
[if !supportLists]·?[endif]META-INF文件夾:程序入口,其中MANIFEST.MF用于描述jar包的信息
[if !supportLists]·?[endif]lib目錄:放置第三方依賴的jar包凫乖,比如springboot的一些jar包
[if !supportLists]·?[endif]spring boot loader相關(guān)的代碼
[if !supportLists]·?[endif]模塊自身的代碼
MANIFEST.MF文件的內(nèi)容:
Manifest-Version: 1.0Implementation-Title: executable-jarImplementation-Version: 1.0-SNAPSHOTArchiver-Version: Plexus ArchiverBuilt-By: FormatStart-Class: spring.study.executablejar.ExecutableJarApplicationImplementation-Vendor-Id: spring.studySpring-Boot-Version: 1.3.5.RELEASECreated-By: Apache Maven 3.2.3Build-Jdk: 1.8.0_20Implementation-Vendor: Pivotal Software, Inc.Main-Class: org.springframework.boot.loader.JarLauncher
我們看到确垫,它的Main-Class是org.springframework.boot.loader.JarLauncher弓颈,當(dāng)我們使用java -jar執(zhí)行jar包的時(shí)候會調(diào)用JarLauncher的main方法帽芽,而不是我們編寫的SpringApplication。
那么JarLauncher這個(gè)類是的作用是什么的翔冀?
它是SpringBoot內(nèi)部提供的工具Spring Boot Loader提供的一個(gè)用于執(zhí)行Application類的工具類(fat jar內(nèi)部有spring loader相關(guān)的代碼就是因?yàn)檫@里用到了)导街。相當(dāng)于Spring Boot Loader提供了一套標(biāo)準(zhǔn)用于執(zhí)行SpringBoot打包出來的jar
Spring Boot Loader抽象的一些類
抽象類Launcher:各種Launcher的基礎(chǔ)抽象類,用于啟動應(yīng)用程序纤子;跟Archive配合使用搬瑰;目前有3種實(shí)現(xiàn),分別是JarLauncher控硼、WarLauncher以及PropertiesLauncher
Archive:歸檔文件的基礎(chǔ)抽象類泽论。JarFileArchive就是jar包文件的抽象。它提供了一些方法比如getUrl會返回這個(gè)Archive對應(yīng)的URL卡乾;getManifest方法會獲得Manifest數(shù)據(jù)等翼悴。ExplodedArchive是文件目錄的抽象
JarFile:對jar包的封裝,每個(gè)JarFileArchive都會對應(yīng)一個(gè)JarFile幔妨。JarFile被構(gòu)造的時(shí)候會解析內(nèi)部結(jié)構(gòu)鹦赎,去獲取jar包里的各個(gè)文件或文件夾谍椅,這些文件或文件夾會被封裝到Entry中,也存儲在JarFileArchive中古话。如果Entry是個(gè)jar雏吭,會解析成JarFileArchive。
比如一個(gè)JarFileArchive對應(yīng)的URL為:
jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/
它對應(yīng)的JarFile為:
/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar
這個(gè)JarFile有很多Entry陪踩,比如:
META-INF/META-INF/MANIFEST.MFspring/spring/study/....spring/study/executablejar/ExecutableJarApplication.classlib/spring-boot-starter-1.3.5.RELEASE.jarlib/spring-boot-1.3.5.RELEASE.jar...
JarFileArchive內(nèi)部的一些依賴jar對應(yīng)的URL(SpringBoot使用org.springframework.boot.loader.jar.Handler處理器來處理這些URL):
jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-starter-web-1.3.5.RELEASE.jar!/jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class
我們看到如果有jar包中包含jar杖们,或者jar包中包含jar包里面的class文件,那么會使 用 !/ 分隔開肩狂,這種方式只有org.springframework.boot.loader.jar.Handler能處 理胀莹,它是SpringBoot內(nèi)部擴(kuò)展出來的一種URL協(xié)議。
JarLauncher的執(zhí)行過程
JarLauncher的main方法:
public?static?void?main(String[]?args)?{????//?構(gòu)造JarLauncher婚温,然后調(diào)用它的launch方法描焰。參數(shù)是控制臺傳遞的????new?JarLauncher().launch(args);}??
JarLauncher被構(gòu)造的時(shí)候會調(diào)用父類ExecutableArchiveLauncher的構(gòu)造方法。
ExecutableArchiveLauncher的構(gòu)造方法內(nèi)部會去構(gòu)造Archive栅螟,這里構(gòu)造了JarFileArchive荆秦。構(gòu)造JarFileArchive的過程中還會構(gòu)造很多東西,比如JarFile力图,Entry …
JarLauncher的launch方法:protected?void?launch(String[]?args)?{??try?{????//?在系統(tǒng)屬性中設(shè)置注冊了自定義的URL處理器:org.springframework.boot.loader.jar.Handler步绸。如果URL中沒有指定處理器,會去系統(tǒng)屬性中查詢????JarFile.registerUrlProtocolHandler();????//?getClassPathArchives方法在會去找lib目錄下對應(yīng)的第三方依賴JarFileArchive吃媒,同時(shí)也會項(xiàng)目自身的JarFileArchive????//?根據(jù)getClassPathArchives得到的JarFileArchive集合去創(chuàng)建類加載器ClassLoader瓤介。這里會構(gòu)造一個(gè)LaunchedURLClassLoader類加載器,這個(gè)類加載器繼承URLClassLoader赘那,并使用這些JarFileArchive集合的URL構(gòu)造成URLClassPath????//?LaunchedURLClassLoader類加載器的父類加載器是當(dāng)前執(zhí)行類JarLauncher的類加載器????ClassLoader?classLoader?=?createClassLoader(getClassPathArchives());????//?getMainClass方法會去項(xiàng)目自身的Archive中的Manifest中找出key為Start-Class的類????//?調(diào)用重載方法launch????launch(args,?getMainClass(),?classLoader);??}??catch?(Exception?ex)?{????ex.printStackTrace();????System.exit(1);??}}//?Archive的getMainClass方法//?這里會找出spring.study.executablejar.ExecutableJarApplication這個(gè)類public?String?getMainClass()?throws?Exception?{??Manifest?manifest?=?getManifest();??String?mainClass?=?null;??if?(manifest?!=?null)?{????mainClass?=?manifest.getMainAttributes().getValue("Start-Class");??}??if?(mainClass?==?null)?{????throw?new?IllegalStateException(????????"No?'Start-Class'?manifest?entry?specified?in?"?+?this);??}??return?mainClass;}//?launch重載方法protected?void?launch(String[]?args,?String?mainClass,?ClassLoader?classLoader)????throws?Exception?{??????//?創(chuàng)建一個(gè)MainMethodRunner刑桑,并把a(bǔ)rgs和Start-Class傳遞給它??Runnable?runner?=?createMainMethodRunner(mainClass,?args,?classLoader);??????//?構(gòu)造新線程??Thread?runnerThread?=?new?Thread(runner);??????//?線程設(shè)置類加載器以及名字,然后啟動??runnerThread.setContextClassLoader(classLoader);??runnerThread.setName(Thread.currentThread().getName());??runnerThread.start();}
MainMethodRunner的run方法:
@Overridepublic?void?run()?{??try?{????//?根據(jù)Start-Class進(jìn)行實(shí)例化????Class<?>?mainClass?=?Thread.currentThread().getContextClassLoader()????????.loadClass(this.mainClassName);????//?找出main方法????Method?mainMethod?=?mainClass.getDeclaredMethod("main",?String[].class);????//?如果main方法不存在募舟,拋出異常????if?(mainMethod?==?null)?{??????throw?new?IllegalStateException(??????????this.mainClassName?+?"?does?not?have?a?main?method");????}????//?調(diào)用????mainMethod.invoke(null,?new?Object[]?{?this.args?});??}??catch?(Exception?ex)?{????UncaughtExceptionHandler?handler?=?Thread.currentThread()????????.getUncaughtExceptionHandler();????if?(handler?!=?null)?{??????handler.uncaughtException(Thread.currentThread(),?ex);????}????throw?new?RuntimeException(ex);??}}
Start-Class的main方法調(diào)用之后祠斧,內(nèi)部會構(gòu)造Spring容器,啟動內(nèi)置Servlet容器等過程拱礁。這些過程我們都已經(jīng)分析過了琢锋。
關(guān)于自定義的類加載器LaunchedURLClassLoader
LaunchedURLClassLoader重寫了loadClass方法,也就是說它修改了默認(rèn)的類加載方式(先看該類是否已加載這部分不變呢灶,后面真正去加載類的規(guī)則改變了吴超,不再是直接從父類加載器中去加載)。LaunchedURLClassLoader定義了自己的類加載規(guī)則:
private?Class<?>?doLoadClass(String?name)?throws?ClassNotFoundException?{??//?1)?Try?the?root?class?loader??try?{????if?(this.rootClassLoader?!=?null)?{??????return?this.rootClassLoader.loadClass(name);????}??}??catch?(Exception?ex)?{????//?Ignore?and?continue??}??//?2)?Try?to?find?locally??try?{????findPackage(name);????Class<?>?cls?=?findClass(name);????return?cls;??}??catch?(Exception?ex)?{????//?Ignore?and?continue??}??//?3)?Use?standard?loading??return?super.loadClass(name,?false);}
加載規(guī)則:
[if !supportLists]·?[endif]如果根類加載器存在鸯乃,調(diào)用它的加載方法鲸阻。這里是根類加載是ExtClassLoader
[if !supportLists]·?[endif]調(diào)用LaunchedURLClassLoader自身的findClass方法,也就是URLClassLoader的findClass方法
[if !supportLists]·?[endif]調(diào)用父類的loadClass方法,也就是執(zhí)行默認(rèn)的類加載順序(從BootstrapClassLoader開始從下往下尋找)
LaunchedURLClassLoader自身的findClass方法:
protected?Class<?>?findClass(final?String?name)?????throws?ClassNotFoundException{????try?{????????return?AccessController.doPrivileged(????????????new?PrivilegedExceptionAction<Class<?>>()?{????????????????public?Class<?>?run()?throws?ClassNotFoundException?{????????????????????//?把類名解析成路徑并加上.class后綴????????????????????String?path?=?name.replace('.',?'/').concat(".class");????????????????????//?基于之前得到的第三方j(luò)ar包依賴以及自己的jar包得到URL數(shù)組赘娄,進(jìn)行遍歷找出對應(yīng)類名的資源????????????????????//?比如path是org/springframework/boot/loader/JarLauncher.class仆潮,它在jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/中被找出????????????????????//?那么找出的資源對應(yīng)的URL為jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class????????????????????Resource?res?=?ucp.getResource(path,?false);????????????????????if?(res?!=?null)?{?//?找到了資源????????????????????????try?{????????????????????????????return?defineClass(name,?res);????????????????????????}?catch?(IOException?e)?{????????????????????????????throw?new?ClassNotFoundException(name,?e);????????????????????????}????????????????????}?else?{?//?找不到資源的話直接拋出ClassNotFoundException異常????????????????????????throw?new?ClassNotFoundException(name);????????????????????}????????????????}????????????},?acc);????}?catch?(java.security.PrivilegedActionException?pae)?{????????throw?(ClassNotFoundException)?pae.getException();????}}
下面是LaunchedURLClassLoader的一個(gè)測試:
//?注冊org.springframework.boot.loader.jar.Handler?URL協(xié)議處理器JarFile.registerUrlProtocolHandler();//?構(gòu)造LaunchedURLClassLoader類加載器,這里使用了2個(gè)URL遣臼,分別對應(yīng)jar包中依賴包spring-boot-loader和spring-boot性置,使用?"!/"?分開,需要org.springframework.boot.loader.jar.Handler處理器處理LaunchedURLClassLoader?classLoader?=?new?LaunchedURLClassLoader(????????new?URL[]?{????????????????new?URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/")????????????????,?new?URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-1.3.5.RELEASE.jar!/")????????},????????LaunchedURLClassLoaderTest.class.getClassLoader());//?加載類//?這2個(gè)類都會在第二步本地查找中被找出(URLClassLoader的findClass方法)classLoader.loadClass("org.springframework.boot.loader.JarLauncher");classLoader.loadClass("org.springframework.boot.SpringApplication");//?在第三步使用默認(rèn)的加載順序在ApplicationClassLoader中被找出classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");
Spring Boot Loader的作用
SpringBoot在可執(zhí)行jar包中定義了自己的一套規(guī)則揍堰,比如第三方依賴jar包在/lib目錄下鹏浅,jar包的URL路徑使用自定義的規(guī)則并且這個(gè)規(guī)則需要使用org.springframework.boot.loader.jar.Handler處理器處理。它的Main-Class使用JarLauncher屏歹,如果是war包隐砸,使用WarLauncher執(zhí)行。這些Launcher內(nèi)部都會另起一個(gè)線程啟動自定義的SpringApplication類蝙眶。
這些特性通過spring-boot-maven-plugin插件打包完成季希。
所謂技多不壓身,我們所讀過的每一本書幽纷,所學(xué)過的每一門語言式塌,在未來指不定都能給我們意想不到的回饋呢。其實(shí)做為一個(gè)開發(fā)者友浸,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要這里我推薦一個(gè)Java學(xué)習(xí)交流群342016322峰尝,不管你是小白還是大牛歡迎入駐,大家一起交流成長收恢。