当使用jar加载springBoot项目时 使用
spring-bootloader.jar插件(23)
BOOT-INF
项目中编写的类和第三方jar
META-INF文件清单
MANIFEST.MF 清单文件 (文件结尾回车换行)
start-Class: com.meishi.sales.SalesBootstrap springboot 提供的类加载器 也就是@springBootAplication注解标示的类
Main-Class: org.springframework.boot.loader.JarLauncher 程序启动入口类
org
来自spring-boot-loader(用于项目的打包和加载)jar包编译之后的内容
jarLauncher源代码分析
JarLauncher 程序启动入口类 将其对应jar加载到内存中 类路径下
*/
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()) {
//如果返回的目录名字 != "BOOT-INF/classes/"
return entry.getName().equals(BOOT_INF_CLASSES);
}
//如果是一个文件 是不是以"BOOT-INF/lib/" 开头
return entry.getName().startsWith(BOOT_INF_LIB);
}
public static void main(String[] args) throws Exception {
//先通过无参构造判断是否为jar再创建归档文件archive再调用父类launcher的launch方法
new JarLauncher().launch(args);
}
}
的
launch()是一个初始的入口点,被其子类的main方法所调用
protected void launch(String[] args) throws Exception {
//处理器 处理jar文件的url
JarFile.registerUrlProtocolHandler();
//获取自定义类加载器UrlClassLoader
ClassLoader classLoader = createClassLoader(getClassPathArchives());
//加载main启动类(命令行参数,要被运行的主类,自定义的URLClassLoader)
launch(args, getMainClass(), classLoader);
}
getClassPathArchives()
protected List<Archive> getClassPathArchives() throws Exception {
//将已经加载的内容放在list集合当中 classse和lib 自己编写的类和第三方jar
List<Archive> archives = new ArrayList<>(
//调用ExecutableArchiveLauncher类中isNestedArchive 抽象方法该方法被
//JarLauncher 子启动器实现
this.archive.getNestedArchives(this::isNestedArchive));
//回调
postProcessClassPathArchives(archives);
//返回符合条件的加载内容"BOOT-INF/classes/" 和 "BOOT-INF/lib/"
return archives;
}
createClassLoader()创建一个加载指定归档文件的类加载器
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(archives.size());
for (Archive archive : archives) {
//添加其URL的完整路径 其就是将Archive文件对象转换成url
urls.add(archive.getUrl());
}
//调用一个加载指定url的一个类加载器
return createClassLoader(urls.toArray(new URL[0]));
}
createClassLoader()
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
//SpringBoot创建的URL类加载器(url数组,和指定的父加载器)
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
getMainClass()
@Override
protected String getMainClass() throws Exception {
//获取文件清单的Mainifest如下图
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
//获取其中的"Start-Class" 指定的class类 也就是当前项目的启动类
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException(
"No 'Start-Class' manifest entry specified in " + this);
}
//返回
return mainClass;
}
的
jar文件规范
- 启动类也就是:Main-Class: org.springframework.boot.loader.JarLauncher
程序启动入口类 指定的Main-Class类必须位于jar包目录顶层不能位于其他目录的子目录里面
- jar文件不能被嵌套 反之为fatJar
传统做法 就是将jar保重带的jar文件中的class类重新拷贝一份 如org中的class类就是其拷贝的
问题
1.jar文件内容混乱
2.依赖的class类名同名不同包 拷贝会被复制
springboot采用自定义类加载器的方式去加载嵌套jar
当以 "BOOT-INF/lib/"开头springboot就知道这是引用第三方jar包
createClassLoader()
**此时调用当前类的launch方法 **
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
//设置上下文类加载器为自定义的URLClassLoader
Thread.currentThread().setContextClassLoader(classLoader);
//创建主方法运行器 赋完值调用run方法
createMainMethodRunner(mainClass, args, classLoader).run();
}
createMainMethodRunner
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,
ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
**MainMethodRunner **
public class MainMethodRunner {
private final String mainClassName;
private final String[] args;
/**
* Create a new {@link MainMethodRunner} instance.
* @param mainClass the main class
* @param args incoming arguments
*/
public MainMethodRunner(String mainClass, String[] args) {
//为其成员变量赋值
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}
public void run() throws Exception {
//使用上下文类加载器去加载启动类 获取其class对象
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
//反射获取springboot项目中的main方法 (sp[) mainClass就是springboot启动类
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
//将数组对象赋值给其mian方法 此时springboot项目开始启动执行
//为什么可以传null 因为一个类的静态方法不属于其所在类只属于其Class类对象
mainMethod.invoke(null, new Object[] { this.args });//静态方法可以传null
}
}
扩展
如上可知springboot的启动流程中 启动类的名字不需要非得是main,起名为main的原因是应为main作为jvm的入口我们可以将其作为独立的程序通过run直接执行(idea右键运行可以直接在程序中运行)
运行
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
System.out.println(MyApplication.class.getClassLoader());
SpringApplication.run(MyApplication.class, args);
}
}
在项目中运行时
系统类加载器
sun.misc.Launcher$AppClassLoader@18b4aac2
使用jar包加载时
自定义类加载器
org.springframework.boot.loader.LaunchedURLClassLoader@439f5b3d