SpringBoot打包后启动分析

在 SpringBoot项目打包结构 中的清单文件(META-INF 文件夹中的 MANIFEST.MF 文件)中定义 jar 包的入口类(Main-Class)指向了 org.springframework.boot.loader.JarLauncher 类。

该类在 org.springframework.boot:spring-boot-loader 包中,要查看需要额外引入,一般开发是通过 SpringBoot 插件生成的,没必要引入。

JarLauncher

继承 ExecutableArchiveLauncher 类

jar包 的 Launcher。此 Launcher 假设项目依赖的 jar包 放在 “/BOOT-INF/lib” 目录中,项目代码的文件放在 “/BOOT-INF/classes” 目录中。

通过 main 方法启动,内容只有一行:

1 new JarLauncher().launch(args);

1. 首先是创建 JarLauncher 示例,而 JarLauncher 的构造方法为空,默认调用父类的构造方法,将成员变量 archive 初始化:

1     public ExecutableArchiveLauncher() {
2         try {
3             this.archive = createArchive();
4         }
5         catch (Exception ex) {
6             throw new IllegalStateException(ex);
7         }
8     }

createArchive() 方法定义在其父类 Launcher 中,创建并返回一个持有指向当前 jar包 的 JarFileArchive对象

 1     protected final Archive createArchive() throws Exception {
 2         ProtectionDomain protectionDomain = getClass().getProtectionDomain();
 3         CodeSource codeSource = protectionDomain.getCodeSource();
 4         URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
 5         String path = (location != null) ? location.getSchemeSpecificPart() : null;
 6         if (path == null) {
 7             throw new IllegalStateException("Unable to determine code source archive");
 8         }
 9         File root = new File(path);
10         if (!root.exists()) {
11             throw new IllegalStateException("Unable to determine code source archive from " + root);
12         }
13         return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
14     }
  • 2 ~ 5 行代码用于获取当前 jar文件 的绝对路径
  • 创建指向当前 jar文件 的 File对象
  • jar文件 非文件夹,传入上面的 File对象,创建并返回一个 JarFileArchive 对象

2. 创建了 JarLauncher对象 后,调用 launch() 方法(定义在 Launcher 类中)

1     protected void launch(String[] args) throws Exception {
2         JarFile.registerUrlProtocolHandler();  // 注册一个 “java.protocol.handler.pkgs” 属性
3         ClassLoader classLoader = createClassLoader(getClassPathArchives());
4         launch(args, getMainClass(), classLoader);
5     }
  • 启动程序的方法,此方法是初始化入口,必须在子类的 main 方法中调用

第2行跳过,分析第3行,首先是 getClassPathArchives() 方法,定义在 Launcher 类中,实现在 ExecutableArchiveLauncher 类中:

1     protected List<Archive> getClassPathArchives() throws Exception {
2         List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
3         postProcessClassPathArchives(archives);
4         return archives;
5     }
  • 首先是 getNestedArchives(EntryFilter) 方法,EntryFilter 接收一个 Archive.Entry 类型的参数,返回一个 boolean 值
  • 然后是 this::isNestedArchive,在 JarLauncher/WarLauncher 中实现,结合类中的常量定义筛选的规则(在 jar包中,该规则为:名称为 “BOOT-INF/classes/” 的文件,或者名称以 “BOOT-INF/lib/” 开头的文件,参考 SpringBoot项目打包结构 ,也就是筛选出依赖包)
  • getNestedArchives(EntryFilter) 在上面提到的 JarFileArchive 中实现,根据对应给定的过滤器,筛选出当前 jar文件 中相关的依赖包等内容,放到一个 list 中,并返回
  • 第4行用于对 list 中的 Archive 进行处理,具体实现为空
  • 返回 list

然后是 createClassLoader 方法:

 1     protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
 2         List<URL> urls = new ArrayList<>(archives.size());
 3         for (Archive archive : archives) {
 4             urls.add(archive.getUrl());
 5         }
 6         return createClassLoader(urls.toArray(new URL[0]));
 7     }
 8     protected ClassLoader createClassLoader(URL[] urls) throws Exception {
 9         return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
10     }
  • 将 List<Archive> 转换成一个 URL数组
  • 传入 URL数组 和指定加载当前类的类加载器作为父加载器,通过 JDK 提供的 URLClassLoader 的构造方法,构造一个 LaunchedURLClassLoader ,并返回

最后是 launch 方法:

1     protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
2         Thread.currentThread().setContextClassLoader(classLoader);
3         createMainMethodRunner(mainClass, args, classLoader).run();
4     }
  • 将线程上下文类加载器设置为 LaunchedURLClassLoader (为了打破双亲委托机制)
  • 然后创建 MainMethodRunner 类,并调用其 run 方法,实际上就是通过反射去运行项目中自定义的启动方法

 

至此,启动工作完成!

 

posted @ 2020-04-13 15:31  飞蛇在水  阅读(622)  评论(0编辑  收藏  举报