Springboot可以说是Java程序员必备技能了,大家都知道Springboot最终可以通过maven打成jar包,然后直接使用javajar命令运行一个Web工程(或其它)。这样就避免了原先基于tomcat的web工程的复杂操作。Springboot能够使Web服务的部署简单到如此程度是因为其内置了Jetty(或Tomcat)服务器,并且在容器启动过程中start该服务器,成功运行Web服务。 本篇并不是深究内置服务器的启动过程,而是追溯Springboot启动之前到底做了什么?它是如何与我们经常写的SpringBootApplication注解注释的main方法类绑定起来的?1、一切的开始 相信各位Springbooter一定不会陌生下面的代码,无论是初学Springboot的新同学,或是开始研究Springboot源码的新司机,这段代码几乎是我们的落脚点。我们如此熟悉它,以至于认为它就是Springboot这个魔法乐园的起点。但真的是这样吗?SpringBootApplicationpublicclassSpringboot01helloworldApplication{publicstaticvoidmain(String〔〕args){SpringApplication。run(Springboot01helloworldApplication。class,args);}}复制代码 我们都知道,一个Java工程打包过后,这个jar包的入口描述被写在了METAINFMANIFEST。MF文件下,下面让我们来看看这个文件内容:ManifestVersion:1。0ArchiverVersion:PlexusArchiverBuiltBy:MrXuStartClass:com。vivo。internet。nex。repeater。console。RepeaterConsoleApplicationSpringBootClasses:BOOTINFclassesSpringBootLib:BOOTINFlibSpringBootVersion:1。5。19。RELEASECreatedBy:ApacheMaven3。8。1BuildJdk:1。8。0281MainClass:org。springframework。boot。loader。JarLauncher复制代码 文件入口的描述为MainClass对应的value,即org。springframework。boot。loader。JarLauncher。那么,接下来我们需要看下这个人类究竟做了什么?JarLauncher。javapublicclassJarLauncherextendsExecutableArchiveLauncher{staticfinalStringBOOTINFCLASSESBOOTINFstaticfinalStringBOOTINFLIBBOOTINFpublicJarLauncher(){}。。。省略无关代码publicstaticvoidmain(String〔〕args)throwsException{(newJarLauncher())。launch(args);}}复制代码 明显的main函数吸引了我们的注意,没错了,这就是入口,看看JarLauncher的空构造并没有任何代码,我们先往它的父类找找:ExecutableArchiveLauncher。javapublicabstractclassExecutableArchiveLauncherextendsLauncher{publicExecutableArchiveLauncher(){try{this。archivethis。createArchive();}catch(Exceptionvar2){thrownewIllegalStateException(var2);}}。。。省略}Launcher。javapublicabstractclassLauncher{publicLauncher(){}。。。省略无关代码}复制代码 从代码中可以看出,真正干了事情的父类是ExecutableArchiveLauncher,它在初始化时构造了archive实例,该实例封装了METAINFMANIFEST。MF文件的信息。后面我们也会用到它。 随后便是launch方法,我们只关系核心执行流程:Launcher。javaprotectedvoidlaunch(String〔〕args)throwsException{JarFile。registerUrlProtocolHandler();ClassLoaderclassLoaderthis。createClassLoader(this。getClassPathArchives());this。launch(args,this。getMainClass(),classLoader);}ExecutableArchiveLauncher。javaprotectedStringgetMainClass()throwsException{Manifestmanifestthis。archive。getManifest();StringmainCif(manifest!null){mainClassmanifest。getMainAttributes()。getValue(StartClass);}if(mainClassnull){thrownewIllegalStateException(NoStartClassmanifestentryspecifiedinthis);}else{returnmainC}}复制代码 这里首先调用子类ExecutableArchiveLauncher的getMainClass方法,主要逻辑就是从METAINFMANIFEST。MF文件中获取StartClass信息,对应上文就是com。vivo。internet。nex。repeater。console。RepeaterConsoleApplication字符串,这样就和我们写的启动类关联上了。 然后是launch方法的具体执行,launch()首先创建一个MainMethodRunner,将上文获取的StartClass和透传的参数传递进去,然后调用MainMethodRunner的run方法。run方法的执行也非常简单,就是加载StartClass对应的启动类,然后反射调用启动类的main方法。之后就是容器的初始化过程了。Launcher。javaprotectedvoidlaunch(String〔〕args,StringmainClass,ClassLoaderclassLoader)throwsException{Thread。currentThread()。setContextClassLoader(classLoader);这里首先调用createMainMethodRunner创建一个MainMethodRunner实例,将mainClass和args参数传入。随后调用this。createMainMethodRunner(mainClass,args,classLoader)。run();}protectedMainMethodRunnercreateMainMethodRunner(StringmainClass,String〔〕args,ClassLoaderclassLoader){returnnewMainMethodRunner(mainClass,args);}MainMethodRunner。javapublicMainMethodRunner(StringmainClass,String〔〕args){this。mainClassNamemainCthis。argsargs!null?(String〔〕)args。clone():}publicvoidrun()throwsException{C?mainClassThread。currentThread()。getContextClassLoader()。loadClass(this。mainClassName);MethodmainMethodmainClass。getDeclaredMethod(main,String〔〕。class);mainMethod。invoke((Object)null,this。args);}复制代码2、总结 综上所述,对于Springboot工程,启动类并不是真正的工程入口,他会被真正的入口反射调用其main方法实现Spring容器的启动。工程入口也是Spring的开发人员为我们营造的一种假象,抽象出来的逻辑入口。 作者:Chub 链接:https:juejin。cnpost7100386958580563982