从原理和源码梳理SpringbootFatJar的机制
一、概述
SpringBootFatJar的设计,打破了标准jar的结构,在jar包内携带了其所依赖的jar包,通过jar中的main方法创建自己的类加载器,来识别加载运行其不规范的目录下的代码和依赖。二、标准的jar包结构
打开Java的Jar文件我们经常可以看到文件中包含着一个METAINF目录,这个目录下会有一些文件,其中必有一个MANIFEST。MF,这个文件描述了该Jar文件的很多信息其中MainClass定义Jar文件的入口类,该类必须是一个可执行的类,一旦定义了该属性即可通过javajarxxx。jar来运行该jar文件。
在生产环境是使用javajarxxx。jar的方式来运行SpringBoot程序。这种情况下,SpringBoot应用真实的启动类并不是我们所定义的带有main方法的类,而是JarLauncher类。查看SpringBoot所打成的FatJar,其MainClass是org。springframework。boot。loader。JarLauncher,这便是微妙之处。SpringBootVersion:2。1。3。RELEASEMainClass:org。springframework。boot。loader。JarLauncherStartClass:com。rock。springbootlearn。SpringbootLearnApplicationSpringBootClasses:BOOTINFclassesSpringBootLib:BOOTINFlibBuildJdk:1。8。0131复制代码
JAR包中的MANIFEST。MF文件详解以及编写规范三、探索JarLauncher
org。springframework。boot。loader。JarLauncher这个类是哪里来的呢?答案在springbootloader。jar包中,可找到这个JarLauncher类的源码。在项目中加入maven依赖,以便查看源码和远程调试。dependencygroupIdorg。springframework。bootgroupIdspringbootloaderartifactIddependency复制代码
认真比较可以看出,这个springbootloader包中的内容与SpringBoot的FatJar包中的一部分内容几乎一样。JarLauncher在jar中的位置如下:
3。1只能拷贝出来一份儿
重点重点重点:因jar规范要求MainClass所指定的类必须位于jar包的顶层目录下,即org。springframework。boot。loader。JarLauncher这个org必须位于jar包中的第一级目录,不能放置在其他的目录下。所以所以所以只能将springbootloader这个jar包的内容拷贝出来,而不是整个jar直接放置于执行Jar中。3。2携带程序所依赖的jar而非仅class
上边JarLauncher的这个org。springframework。xx以及METAINF这两个目录是符合jar包规范的。但是BOOTINF这个目录里边有点像我们开发中的一些用法:依赖jar包在lib目录下但按照jar包规范jar中不能有jar包的情况下程序。class文件在classes目录下但xxx。class文件应该按照org。springframework。xx这样放置在jar中的根目录中
所以classes和lib你也能意识到,这个设计是独特的。早期jar包内携带依赖是采用如mavenshadeplugin的做法,把依赖的class文件拷贝到目标jar中,但也会造成重名(全限定名)的类会出现覆盖的情况。后来SpringBoot为了避免覆盖的情况,修改了打包机制,放弃了mavenshadeplugin那种拷贝class的方式,调整为依赖原始jar包;这同时意味着改变了Jar标准的运行机制,那么要想让classes和lib中代码能够正常运行,你试想一下如果没有自定义的classLoader来加载这些类文件,可以嘛?四、自定义类加载器的运行机制
自定义类加载器的常规处理:指定资源指定委托关系指定线程上下文类加载器调用逻辑入口方法4。1指定资源
构造方法中基于jar包的文件系统信息,构造Archive对象publicExecutableArchiveLauncher(){this。archivecreateArchive();}protectedfinalArchivecreateArchive()throwsException{ProtectionDomainprotectionDomaingetClass()。getProtectionDomain();CodeSourcecodeSourceprotectionDomain。getCodeSource();URIlocation(codeSource!null)?codeSource。getLocation()。toURI():null;Stringpath(location!null)?location。getSchemeSpecificPart():null;if(pathnull){thrownewIllegalStateException(Unabletodeterminecodesourcearchive);}FilerootnewFile(path);if(!root。exists()){thrownewIllegalStateException(Unabletodeterminecodesourcearchivefromroot);}return(root。isDirectory()?newExplodedArchive(root):newJarFileArchive(root));}复制代码
采集jar包中的classes和lib目录下的归档文件。后边创建ClassLoader的时候作为参数传入OverrideprotectedListgetClassPathArchives()throwsException{ListarchivesnewArrayList(this。archive。getNestedArchives(this::isNestedArchive));postProcessClassPathArchives(archives);returnarchives;}protectedbooleanisNestedArchive(Archive。Entryentry){if(entry。isDirectory()){returnentry。getName()。equals(BOOTINFCLASSES);}returnentry。getName()。startsWith(BOOTINFLIB);}复制代码publicstaticvoidmain(String〔〕args)throwsException{newJarLauncher()。launch(args);}复制代码4。2创建自定义ClassLoaderprotectedvoidlaunch(String〔〕args)throwsException{JarFile。registerUrlProtocolHandler();创建类加载器,并指定归档文件ClassLoaderclassLoadercreateClassLoader(getClassPathArchives());launch(args,getMainClass(),classLoader);}创建类加载器,将归档文件转换为URLprotectedClassLoadercreateClassLoader(Listarchives)throwsException{ListURLurlsnewArrayList(archives。size());for(Archivearchive:archives){urls。add(archive。getUrl());}returncreateClassLoader(urls。toArray(newURL〔0〕));}父加载器是AppClassLoaderprotectedClassLoadercreateClassLoader(URL〔〕urls)throwsException{getClass()。getClassLoader()是系统类加载器,因为默认情况下main方法所在类是由SystemClassLoader加载的,默认情况下是AppClassLoader。returnnewLaunchedURLClassLoader(urls,getClass()。getClassLoader());}复制代码4。3设置线程上下文类加载器,调用程序中的mainclasspublicstaticvoidmain(String〔〕args)throwsException{newJarLauncher()。launch(args);}protectedvoidlaunch(String〔〕args,StringmainClass,ClassLoaderclassLoader)throwsException{设置线程上下文类加载器Thread。currentThread()。setContextClassLoader(classLoader);调用MANIFEST。MF中配置的StartClass:xxx的main方法,还带入了参数createMainMethodRunner(mainClass,args,classLoader)。run();}复制代码
岳飞的悲剧岳飞岳飞含冤而死的时候不过39岁,此时的官职已经是武将最高级了,按今天的职级来说就是元帅级别的人了,所统领的军队是南宋的主力,南宋唯一骑兵队伍就在岳飞的统领之下。岳飞主张抗金,主张
能走南北二京,不走楚旺回隆?不走神标豆公?过去,冀南豫北流传一句话,能走南北二京,不走楚旺回隆,但是这句话还有另外几个版本,也有说不走泊口回隆,还有说不走神庙豆公,不走施济故城等等,总之,这些俗语都说明了冀南豫北一带治安不
明代皇权在不断的重构中,有遇到哪些困境吗?如何解决的?公元1368年建立的明王朝不仅仅在文化上奉行驱逐胡虏,恢复中华的华夏本位主义政策,更是在政制上取法唐宋,奉天法古,力图恢复周制这一几千年来儒家的理想政治制度,从而重构起一个以绝对皇
奥古斯都,罗马的第一位皇帝,他带领罗马度过了混乱文思问录编辑思问录公元1914年4月11日,罗马最有影响力的人世界历史上最重要的人物之一去世。据报道,他的遗言是我发现罗马是一座砖城,而留下了一座大理石城。没有什么能更好地描述奥古
云南回民起义(40)第一次寻甸会战寻甸,嵩明位于昆明城东北方向,是从东北方向进入昆明的门户,具有极其重要的战略位置。1868年3月,滇西回民起义军进至昆明城郊,已经降清的滇东,滇南回民起义军一部反正响应杜文秀部回军
春秋自这一标志性事件起名义上,周王室是中央政权,郑国是其下属的诸侯国,并不平等的双方却互换人质,岂非咄咄怪事?实际上,自周幽王烽火戏诸侯以及周平王东迁雒邑以来,周天子的权威已经崩溃,但周王及各诸侯都没将
成都一郊区33年管辖14个县,堪称巴蜀第一,如今备受冷落?每个人,每个地方都有属于自己曾经的荣光,城市当然也是如此。都作为当下中国最著名的城市之一,几乎无人不知无人不晓,但是很多人不知道历史上有很长一段时间,成都西边有一个区反而更加壮大,
刘邦为什么要杀死韩信?之所以我的汉高祖刘邦能创下宏伟大业,还得靠那些将领们的牺牲来换的,可是韩信是刘邦最得力的部下,可以韩信最后为什么会被刘邦杀死呢?难道是因为韩信太强了吗?请听我慢道来,嘿嘿害羞害羞耶
完颜宗弼给秦桧的那封信里究竟写了什么金国的都天下兵马元帅完颜宗弼,江湖惯称金兀术,给南宋太师宰相秦桧写了一封信。这是代表官方写的一封很私密的信,一场命案的发生就是因为这封信。信很简单。写信人想把它交付到收信人手里,但
蓝戟英特尔锐炫A750显卡价格调整为2099元,降价400元IT之家2月2日消息,英特尔昨日宣布,2月1日开始,英特尔锐炫A750公版显卡的全球价格将从289美元调整为249美元(当前约1683元人民币)。英特尔公版型号国行价格已调整为21
小棋童和大师携手出场五羊杯开幕式展现薪火传承54321第30届五羊杯全国象棋冠军邀请赛于2月1日晚在广州文化公园开幕。当柳大华吕钦王天一郑惟桐等象棋大师和棋童走出舞台,台下掌声雷动棋迷兴奋不已。象棋大师和棋童一起出场是本次五