广告位联系
返回顶部
分享到

SpringBoot的jar包启动的实现方法

java 来源:互联网 作者:秩名 发布时间:2022-03-20 18:07:28 人浏览
摘要

一、简介 ? 使用过SprongBoot打过jar包的都应该知道,目标文件一般都会生成两个文件,一个是以.jar的包,一个是.jar.original文件。那么使用SpringBoot会打出两个包,而.jar.original的作用是什

一、简介

? 使用过SprongBoot打过jar包的都应该知道,目标文件一般都会生成两个文件,一个是以.jar的包,一个是.jar.original文件。那么使用SpringBoot会打出两个包,而.jar.original的作用是什么呢?还有就是java -jar是如何将一个SpringBoot项目启动,之间都进行了那些的操作?

? 其实.jar.original是maven在SpringBoot重新打包之前的原始jar包,内部只包含了项目的用户类,不包含其他的依赖jar包,生成之后,SpringBoot重新打包之后,最后生成.jar包,内部包含了原始jar包以及其他的引用依赖。以下提及的jar包都是SpringBoot二次加工打的包。

二、jar包的内部结构

SpringBoot打出的jar包,可以直接通过解压的方式查看内部的构造。一般情况下有三个目录。

  • BOOT-INF:这个文件夹下有两个文件夹classes用来存放用户类,也就是原始jar.original里的类;还有一个是lib,就是这个原始jar.original引用的依赖。
  • META-INF:这里是通过java -jar启动的入口信息,记录了入口类的位置等信息。
  • org:Springboot loader的代码,通过它来启动。

这里主要介绍一下/BOOT-INF/MANIFEST.MF文件

1

2

Main-Class: org.springframework.boot.loader.JarLauncher

Start-Class: cn.com.springboot.center.AuthEenterBootstrap

Main-Class:记录了java -jar的启动入口,当使用该命令启动时就会调用这个入口类的main方法,显然可以看出,Springboot转移了启动的入口,不是用户编写的xxx.xxx.BootStrap的那个入口类。

Start-Class:记录了用户编写的xxx.xxx.BootStrap的那个入口类,当内嵌的jar包加载完成之后,会使用LaunchedURLClassLoader线程加载类来加载这个用户编写的入口类。

三、加载过程

1.使用到的一些类

3.1.1 Archive

? 归档文件接口,实现迭代器接口,它有两个子类,一个是JarFileArchive对jar包文件使用,提供了返回这个jar文件对应的url、或者这个jar文件的MANIFEST文件数据信息等操作。是ExplodedArchive是文件目录的使用也有获取这个目录url的方法,以及获取这个目录下的所有Archive文件方法。

3.1.2 Launcher

? 启动程序的基类,这边最后是通过JarLauncher#main()来启动。ExecutableArchiveLauncher是抽象类,提供了获取Start-Class类路径的方法,以及是否还有内嵌对应文件的判断方法和获取到内嵌对应文件集合的后置处理方法的抽象,由子类JarLauncher和WarLauncher自行实现。

3.1.3 Spring.loader下的JarFile和JarEntry

? jarFile继承于jar.util.jar.JarFile,JarEntry继承于java.util.jar.JarEntry,对原始的一些方法进行重写覆盖。每一个JarFileArchive都拥有一个JarFile方法,用于存储这个jar包对应的文件,而每一个JarFile都有一个JarFileEntries,JarFileEntries是一个迭代器。总的来说,在解析jar包时,会将jar包内的文件封装成JarEntry对象后由JarFile对象保存文件列表的迭代器。所以JarFileArchive和JarFileEntries之间是通过JarFile连接,二者都可以获取到JarFile对象。

2.过程分析

从MANIFEST.MF文件中的Main-class指向入口开始。

创建JarLauncher并且通过它的launch()方法开始加载jar包内部信息。

1

2

3

public static void main(String[] args) throws Exception {

  new JarLauncher().launch(args);

}

JarLauncher的空构造方法时一个空实现,刚开始看的时候还懵了一下,以为是在后续的操作中去加载的文件,其实不然,在创建时由父类ExecutableArchiveLauncher的构造方法去加载的文件。

加载为归档文件对象:

1

this.archive = createArchive();

具体的加载方法:判断路径是否是一个文件夹,是则返回ExplodedArchive对象,否则返回JarFileArchive 进入JarFileArchive类:通过这个new方法创建JarFile对象

1

2

3

4

5

6

7

public class JarFileArchive implements Archive {

 

  public JarFileArchive(File file, URL url) throws IOException {

    this(new JarFile(file));

    this.url = url;

  }

}

进入到JarFile方法:通过RandomAccessDataFile读取文件的内容,并传递给本类中的方法进行具体的解析。

1

2

3

4

5

6

public class JarFile extends java.util.jar.JarFile {

 

  public JarFile(File file) throws IOException {

    this(new RandomAccessDataFile(file));

  }

}

进入jarLauncher的launch方法:注册URL协议的处理器,没有指定时,默认指向org.springframework.boot.loader包路径,获取类路径下的归档文件Archive并通过这些归档文件的URL,创建线程上下文类加载器,使用类加载器和用户编写的启动入口类,通过反射调用它的main方法。

1

2

3

4

5

protected void launch(String[] args) throws Exception {

  JarFile.registerUrlProtocolHandler();

  ClassLoader classLoader = createClassLoader(getClassPathArchives());

  launch(args, getMainClass(), classLoader);

}

JarLauncher的getClassPathArchives是在ExecutableArchiveLauncher中实现:获取归档文件中满足EntryFilterg过滤器的项,isNestedArchive方法由具体的之类实现。获取到当前归档文件下的所有子归档文件之后的后置操作,是一个扩展点。在JarLauncher中是一个空实现。

JarLauncher的具体实现,这里通过判断是否在BOOT-INF/lib/包下返回true 也就是说只会把jar包下的BOOT-INF/lib/下的文件加载为Archive对象

1

2

3

4

5

6

protected boolean isNestedArchive(Archive.Entry entry) {

  if (entry.isDirectory()) {

    return entry.getName().equals(BOOT_INF_CLASSES);

  }

  return entry.getName().startsWith(BOOT_INF_LIB);

}

JarFileArchive的getNestedArchives方法:若匹配器匹配到则获取内嵌归档文件。

具体的获取内嵌归档文件逻辑:根据具体的Entry对象,创建JarFile对象并封装成归档文件对象后返回。

1

2

3

4

5

6

protected Archive getNestedArchive(Entry entry) throws IOException {

    try {

        JarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);

        return new JarFileArchive(jarFile);

    }

}

获取到参数entry对应的RandomAccessData对象,这里根据springboot扩展的url协议,在父路径的基础上添加!/来标记子包。

1

2

3

4

5

private JarFile createJarFileFromFileEntry(JarEntry entry) throws IOException {

    RandomAccessData entryData = this.entries.getEntryData(entry.getName());

    return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName(),

                       entryData, JarFileType.NESTED_JAR);

}

到这基本上读取jar内部信息,加载为对应归档文件对象的大概过程已经讲完了,接下来分析一下在获取到了整个jar的归档文件对象后的处理。

通过归档文件对象列表,获取对应的url信息,并通过url信息创建LaunchedURLClassLoader

1

2

3

4

5

6

7

protected ClassLoader createClassLoader(List<Archive> archives) {

    List<URL> urls = new ArrayList<URL>(archives.size());

    for (Archive archive : archives) {

        urls.add(archive.getUrl());

    }

    return createClassLoader(urls.toArray(new URL[urls.size()]));

}

获取到对应的LaunchedUrlClassLoader类加载器之后,设置线程的上下文类加载器为该加载器。根据MANIFI.MF文件中的start-classs信息创建项目启动入口主类对象,并通过返回对象的run方法启动

1

2

3

4

protected void launch(String[] args, String mainClass, ClassLoader classLoader) {

    Thread.currentThread().setContextClassLoader(classLoader);

    createMainMethodRunner(mainClass, args, classLoader).run();

}

进入MainMethodRunner的run方法:先通过当前线程获取到main入口类,然后通过反射调用启动项目启动类的main方法

1

2

3

4

5

6

public void run() throws Exception {

    Class<?> mainClass = Thread.currentThread().getContextClassLoader()

                    .loadClass(this.mainClassName);

    Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);

    mainMethod.invoke(null, new Object[] { this.args });

}

最后来说一下这个LaunchedURLClassLoader,它继承于URLClassLoader,并重写了loadClass方法

LaunchedClassLoader的loadClass方法:调用父类loadClass方法,走正常委派流程,最终会被LaunchURLClassLoader加载。

1

2

3

4

5

6

7

8

9

@Override

protected Class<?> loadClass(String name, boolean resolve){

   try {

      try {

         definePackageIfNecessary(name);

      }

      return super.loadClass(name, resolve);

   }

}

进入URLClassLoader中根据springboot解析进行解析。根据名称将路径转化为以.class结尾的/分隔的格式。通过UrlClassPath对象根据路径获取资源类文件

1

2

3

4

5

6

7

8

9

10

11

new PrivilegedExceptionAction<Class<?>>() {

    public Class<?> run() throws ClassNotFoundException {

      String path = name.replace('.', '/').concat(".class");

      Resource res = ucp.getResource(path, false);

      if (res != null) {

        try {

          return defineClass(name, res);

        }

      }

    }

  }

四、总结

? Springboot主要实现了对URL加载方式进行了扩展,并且对一些对象Archive、JarFile、Entry等进行了抽象和扩展,最后使用LaunchedUrlClassLoader来进行处理。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://juejin.cn/post/7066401642412769310
相关文章
  • SpringBoot自定义错误处理逻辑介绍

    SpringBoot自定义错误处理逻辑介绍
    1. 自定义错误页面 将自定义错误页面放在 templates 的 error 文件夹下,SpringBoot 精确匹配错误信息,使用 4xx.html 或者 5xx.html 页面可以打印错误
  • Java实现手写一个线程池的代码

    Java实现手写一个线程池的代码
    线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和
  • Java实现断点续传功能的代码

    Java实现断点续传功能的代码
    题目实现:网络资源的断点续传功能。 二、解题思路 获取要下载的资源网址 显示网络资源的大小 上次读取到的字节位置以及未读取的字节
  • 你可知HashMap为什么是线程不安全的
    HashMap 的线程不安全 HashMap 的线程不安全主要体现在下面两个方面 在 jdk 1.7 中,当并发执行扩容操作时会造成环形链和数据丢失的情况 在
  • ArrayList的动态扩容机制的介绍

    ArrayList的动态扩容机制的介绍
    对于 ArrayList 的动态扩容机制想必大家都听说过,之前的文章中也谈到过,不过由于时间久远,早已忘却。 所以利用这篇文章做做笔记,加
  • JVM基础之字节码的增强技术介绍

    JVM基础之字节码的增强技术介绍
    字节码增强技术 在上文中,着重介绍了字节码的结构,这为我们了解字节码增强技术的实现打下了基础。字节码增强技术就是一类对现有字
  • Java中的字节码增强技术

    Java中的字节码增强技术
    1.字节码增强技术 字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。 参考地址 2.常见技术 技术分类 类
  • Redis BloomFilter布隆过滤器原理与实现

    Redis BloomFilter布隆过滤器原理与实现
    Bloom Filter 概念 布隆过滤器(英语:Bloom Filter)是1970年由一个叫布隆的小伙子提出的。它实际上是一个很长的二进制向量和一系列随机映射
  • Java C++算法题解leetcode801使序列递增的最小交换次

    Java C++算法题解leetcode801使序列递增的最小交换次
    题目要求 思路:状态机DP 实现一:状态机 Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minSwap(int[] nums1, int[] nums2) { int n
  • Mybatis结果集映射与生命周期介绍

    Mybatis结果集映射与生命周期介绍
    一、ResultMap结果集映射 1、设计思想 对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了 2、resultMap的应用场
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计