java项目启动研究

一直以来都是通过tomcat启动java应用,从未考虑过应用具体启动流程。本例分析从main函数启动的方式,加深对java底层执行的理解。
一、 使用java命令启动xxx.java
1. 首先用javac编译文件
2. 使用 java MainClass.class 执行文件,我首次尝试的时候总是抛出异常:
错误: 找不到或无法加载主类 MainClass
MainClass.java 内容如下:
package com.yzb808.main;
public class MainClass {
	public static void main(String[] args) {
		System.out.println("hello!");
	}
}
       之所以抛出上述异常的原因,是因为 MainClass.java 中写明了包路径(package com.yzb808.main),java 执行时会在当前执行目录下寻找 com/yzb808/main 相对目录,再在相对目录下寻找 MainClass.class 文件。
例如我把 MainClass.java 拷贝到 E:// 目录下,在该目录下执行 javac,会在目录里生成 MainClass.class 文件,再在目录下执行 java MainClass 就会抛出上面的错误。要解释这个现象要从java加载class文件的方式说起。java会从classPath里寻找class(注意寻找的是类全名,包名对应文件路径),缺省情况下:
1. 先从java命令执行的目录下寻找
2. 再查找 %JAVA_HOME%\lib
对应到上述情况,java 命令会生硬的在当前目录下寻找 ./com/yzb808/main/MainClass.class 去执行。因此对于这种有包路径的java文件,编译后在执行目录下建一层包名路径,再执行:
java com/yzb808/main/MainClass 就不会报错了。

二、启动jar文件
java -jar xxx.jar
这条命令会启动一个jar包作为java应用,入口依然在main方法上。java命令如何找到应该从jar包的哪个main方法启动?步骤如下:
1. 解析jar包根目录下 META-INF/MANIFEST.MF 文件,该文件内容是类properties的kv结构。
2. 寻找文件中 ‘Main-Class’ 指向的类,该类中的main方法就是用于启动的main方法
这样能启动一个简单的java应用,但通常情况java应用还有很多依赖(不仅是jre),如何将依赖文件也打包在一起并能被探测执行,有以下四种思路:
a. 使用mvn插件 maven-shade-plugin,将依赖包拆开,和用户代码合在一起打成独立jar包(还可以在三方包路径上加前缀,以避开包冲突,常用在中间件)。
b. 使用mvn命令 dependency:copy-dependencies,将依赖jar和应用自己的jar放在同一目录下,使用 maven-jar-plugin 插件定制 MENIFEST.MF,最终在该目录中启动java应用(这种方式要生成独立可运行的jar包,通常还需要配合发布系统的压缩和解压)。
c. 打war包,依赖容器部署,这种方式用户没有main入口,而是借由web.xml启动的一套遵循servlet规范的应用。
d. 用springboot mvn插件 spring-boot-maven-plugin,打出jar包MENIFEST.MF中 Main-Class 指向 org.springframework.boot.loader.JarLauncher,这是一个强行打包到jar根目录的文件,来自spring-boot-loader。而用户的类放在BOOT-INF/classes,依赖的jar包放在BOOT-INF/lib。这些路径随后会被spring加入class-path(加入spring boot提供的classLoader对象)。
默认情况下,maven打包出来的jar并没有 Main-Class 值,除了用户自己编辑外,还能利用各类mvn插件实现自动注入。
之前对jar文件的理解也比较模糊,因此生出很多不正常的打包需求。jar虽然是一种压缩方式,但对于能被java命令识别解析的jar文件,必须符合java寻找类的规范,也即正确的包路径加完整的类名和符合规范的元数据录入方式。

测试发现使用 spring-boot-maven-plugin 插件打独立可运行的jar包最方便,该插件可以自动查找包含main方法的类,也可以由用户显式指定。打包出来的jar可以被直接执行,用户class和依赖的jar包目录也很清晰。但需要注意的一点,这种启动方式改变了默认的classLoader为 org.springframework.boot.loader.LaunchedURLClassLoader,虽然目前并没有发现有什么问题。
LaunchedURLClassLoader是spring定义的ClassLoader,在jar包初始化时被创建,其父类是常规的AppClassLoader,spring-boot加载过程中会将 'BOOT-INF/classes/'目录 和 'BOOT-INF/lib/'目录下的所有文件封装成URL插入到classLoader中。classLoader组装成功后,会被设置到当前线程里:
Thread.currentThread().setContextClassLoader(classLoader);
再通过该classLoader加载用户的携带main方法的类,并在当前线程通过反射回调main方法,至此springBoot的工作完成,这个classLoader会伴随用户执行的各个阶段。在用户代码执行阶段,classLoader URL中的resouce都能被检索到(这一段逻辑在 org.springframework.boot.loader.JarLauncher 中可以看到)。

测试代码见 jar 项目(https://github.com/yzb808/jar)。

猜你喜欢

转载自blog.csdn.net/yzb808/article/details/80854993