spring boot中jar包方式运行主要依赖于,打包后在jar归档文件根目录的org.springfreamwork.boot.loader.JarLauncher类中执行main方法。
JarLauncher是其中的一个启动引导类,除此以外还有warLauncher,但我们不做过多探讨;
说说jar启动,首先打包后的jar存在3个目录
1 boot-info目录,该目录是当前的自己业务代码的核心目录,里面包含class文件夹和lib文件夹,也就是你的项目类加载目录和jar的依赖包目录。
2 org.springfreamwork.boot.loader文件夹,这个文件夹是jar项目的启动项目;到这里我们意识到了,jar启动并不是直接去启动我们写的项目,而是通过JarLauncher间接启动我们自己的项目。
3 mate-info目录,这里面有该jar包启动所需要的清单文件,MANIFEST.MF 。
MANIFEST中有2个配置项最关键。分别是main-class,start-class。main-class的值就是org.springfreamwork.boot.loader.JarLauncher,而start-class才是我们自己项目中定义的静态main方法。
那么springboot为什么这么设计呢?
之前我们提到了,jar归档文件中有3个文件夹,那么我们的项目实际在boot-info目录中,如果直接通过java -jar的方式去启动则jar本身的内部文件形式并不是标准的jar包;实际是没法使用的,毕竟jar中包含的文件还存在lib文件夹,而lib中仍然有jar包,这显然不是一个我们常规意义上标准的jar包形式。
所以springboot才使用了,将其最根本的启动项目放在jar归档文件的根目录中也就是,org.springfreamwork.boot.loader这个项目。这样就可以通过java -jar的方式去启动springboot中约定好的项目;当JarLauncher启动后,在使用自定义类加载器去加载我们应用的classes资源和lib资源,从而反射调用我们应用的start-class作为入口方法去启动我们用户的引用。
protected void launch(String[] args) throws Exception { JarFile.registerUrlProtocolHandler(); ClassLoader classLoader = createClassLoader(getClassPathArchives()); launch(args, getMainClass(), classLoader); }
上面的代码是启动时的核心代码,创建类加载器,加载资源后反射调用start-class方法。
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { Thread.currentThread().setContextClassLoader(classLoader); createMainMethodRunner(mainClass, args, classLoader).run(); }
而且我们通过launch方法也可以发现,创建类加载器后将类加载器绑定到当前线程,也就是又使用了上下文类加载器,如上面的代码,而下面的代码则是通过上下文类加载器反射静态方法执行用户应用的启动
public MainMethodRunner(String mainClass, String[] args) { this.mainClassName = mainClass; this.args = (args != null) ? args.clone() : null; } public void run() throws Exception { Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); mainMethod.invoke(null, new Object[] { this.args }); }
到这里springboot,巧妙的通过start-class和main-class(也就是JarLauncher)将非标准的jar归档文件,变成了可间接执行的项目。而且没有破坏本身的项目结构,可谓是设计的十分巧妙。