SpringBoot深入理解-项目打包SpringBoot启动过程

当使用打包时,会下载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不归存与类中,它只是把类当做寄存的场所而已,原因是所有的方法必须存在与类中

欢迎大家私信博主,拉你进技术交流群。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值