当使用打包时,会下载org-springframework-boot-loader的jar,并且不会放在lib存放的第三方jar包文件中,该jar包中有个JarLauncher.class文件中设置了jar包运行时的入口和打包后文件的结构(定义了BOOT-INF,META-INF中放什么数据)
使用java -jar 启动项目时,因为META-INF中文件记载了启动的顺序
Manifest-Version: 1.0 #版本
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Start-Class: com.zyy.gradletest1.GradleTest1Application #项目的启动器
Spring-Boot-Classes: BOOT-INF/classes/ #记录编译后文件存放地址
Spring-Boot-Lib: BOOT-INF/lib/ #记录第三方jar包存放地址
Spring-Boot-Version: 2.3.0.RELEASE #SpringBoot版本
Main-Class: org.springframework.boot.loader.JarLauncher #标记了程序的入口,程序的入口定义类加载器去加载项目的启动器
所以程序会直接进入JarLauncher.class中执行main方法
public class JarLauncher extends ExecutableArchiveLauncher {
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
public JarLauncher() {
}
protected JarLauncher(Archive archive) {
super(archive);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
JarLancher继承了ExecutableArchiveLauncher,ExecutableArchiveLauncher抽象类是归档启动器的父类,它有两个子类
Lancher:是整个归档的基类(抽象类)
|
ExecutableArchiveLauncher
| |
JarLancher WarLancher
顾名思义jarLancher就是打包成jar的归档启动类,WarLancher是War打包方式的归档启动类。
当执行JarLancher的main方法时,会调用Lancher基类的launch方法,该方法注释为:启动应用程序。 此方法是初始入口点应该由一个子类被称为public static void main(String[] args)方法。
使用SpringBoot自定义类加载器(LaunchedURLClassLoader)来加载打包好的Jar文件中BOOT-INF里面的类和lib
ClassLoader classLoader = createClassLoader(getClassPathArchives());
getClassPathArchives()方法:返回嵌套Archive S表示匹配指定过滤器条目。
意思就是:获得需要自定义类加载器需要加载的类的路径并返回(归档文件)
public abstract class ExecutableArchiveLauncher extends Launcher {
@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<>(
/*this::isNestedArchive:确定指定JarEntry是应添加到类路径嵌套项。 该方法被调用一次为每个条目。
说人话就是查看当前需要加载的路径是否符合,如果符合返回true,这个方法调用的就是JarLancher类 中的isNestedArchive方法*/
this.archive.getNestedArchives(this::isNestedArchive));
postProcessClassPathArchives(archives);
//返回Archive类型的集合,Archive是可以由{@link Launcher}启动的档案类,里面记录的需要加载类的路径
return archives;
}
}
补充知识:
正常的jar包加载的方式就是直接找到classpath下的顶层路径加载(例如:org.springframework)
传统的打包方式是将第三方包和自己写的代码打包在一块,这就有可能出现,包名冲突之类的。为了解决这种问题,Spring的打包方式就是把第三方包和自己的代码分开。但是此时就出现了一个问题,不能正常找到顶层的加载文件,那么spring就出现了org-springframework-boot-loader包,spring默认规定该包在打包时将该包放到classpath下(顶层路径),首先加载该类中对应的Lancher类,然后通过该类创建的自定义类加载器去加载项目中其他的类。
准备去加载项目的主运行程序
launch(args, getMainClass(), classLoader);
getMainClass():获得ExecutableArchiveLauncher.Archive类中的清单,Archive在创建自定义类加载器时,被构造方法初始化,初始化的路径就是classpath路径下的META-INF文件中的信息然后读取对应的主程序入口的路径(也就是META-INF文件中的信息)key为Start-Class
的值(就是项目主程序的启动器)
public abstract class ExecutableArchiveLauncher extends Launcher {
private final Archive archive;
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@Override
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.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方法中
public abstract class Launcher {
/**
* Launch the application given the archive file and a fully configured classloader.
* @param args the incoming arguments
* @param mainClass the main class to run
* @param classLoader the classloader
* @throws Exception if the launch fails
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
//将当前线程的上下文设置为自定义类加载器
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
}
创建一个运行Main的县城,将运行产参数和类加载器传入进去
createMainMethodRunner(mainClass, args, classLoader)
/**
* Create the {@code MainMethodRunner} used to launch the application.
* @param mainClass the main class main方法运行的路径
* @param args the incoming arguments 方法的参数
* @param classLoader the classloader 自定义类加载器
* @return the main method runner 返回线程
*/
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,
ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
当线程创建好之后!!!重点来了!!!!运行run方法
createMainMethodRunner(mainClass, args, classLoader).run();
public void run() throws Exception {
//获得线程上下文取出类加载器,然后获得要运行的main方法的路径
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
//通过反射定位到main方法的位置
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
//调用对应的main方法(此时调用的就是SpringBoot项目的启动器)
mainMethod.invoke(null, new Object[] { this.args });
}
注意:mainMethod.invoke(null, new Object[] { this.args });
为什么invoke第一个参数是空,因为main方法是static的,static不归存与类中,它只是把类当做寄存的场所而已,原因是所有的方法必须存在与类中
欢迎大家私信博主,拉你进技术交流群。