SpringBoot: 启动流程和类装载

前面我们学过Spring定制了自己的可执行jar,将真正执行时需要的类和依赖放到BOOT-INF/classes、BOOT-INF/lib来,为了能够识别这些为止的源文件,Spring定制了自己类加载器,本节我们来讲解这个类加载器。本节涉及的内容主要包括:

  1. SpringBoot启动流程
  2. SpringBoot类加载器

1. SpringBoot启动流程

在SpringBoot的可执行jar中,配置了MANIFEST.MF的Main-Class设置为JarLauncher,JarLauncher的加载过程如下图

  1. 调用Launcher.launch方法,开始引导程序
  2. 通过Handlers.reigster()注册URLStreamHandler的jar协议处理类,支持getResource读取BOOT-INF/下的内容
  3. 通过archive.getClassPathUrls()筛选可执行jar的所有元素,通过JarLauncher.isIncludedOnClassPath过滤,筛选出所有ClassPath元素
  4. 用这些筛选的ClassPath创建LaunchedClassLoader
  5. 使用LaunchedClassLoader加载MANIFEST.MF的Start-Class
  6. 反射调用main方法

2. SpringBoot类加载器

使用URLClassLoader加载SpringBoot Jar时不会加载BOOT-INF下的classes、lib的类和依赖包,SpringBoot通过自定义类加载器LaunchedClassLoader解决这个问题。它继承自URLClassLoader。参照SpringBoot的启动流程,我们讲手动使用LaunchedClassLoader加载可执行jar,并尝试引导SpringBoot应用。

1. 创建Archive

从启动流程可以看到,核心流程是先通过Archive.getClassPathUrls()获取所有的ClassPath,包括jar文件中内嵌的。JarFileArchive是包可见的,用Archive.create创建实例

URLClassLoader ucl = new URLClassLoader("MyURLClassLoader", new URL[]{url}, null);
Class<?> clazz = ucl.loadClass("org.springframework.boot.loader.JarLauncher");
Archive archive = Archive.create(clazz);
2. 枚举内嵌Jar

Archive提供了getClassPathUrls筛选所有的Archive.Entry,Archive和Archive.Entry可以按JarFile、JarEntry来理解,正是它们有不同的抽象,不仅仅支持jar文件。

Set<URL> su = archive.getClassPathUrls((Archive.Entry e) -> true);
su.stream().forEach(x -> System.out.println(x));

这个方法的输出是这样的,它会是一些jar:nested:协议的URL集合。我们知道URLClassLoader接收一组根路径实现类加载,把这个集合都传递给URLClassLoader是不是就可以了呢?

3. 使用内嵌Jar

讲上一步获取的Set作为根路劲提供给URLClassLoader

URLClassLoader lcl = new URLClassLoader("MyNested", su.toArray(URL[]::new),null);
clazz = lcl.loadClass("com.keyniu.yangsi.YangsiApplication");
System.out.println(clazz);

并不能正常工作,因为jar:nested不是Java支持的内嵌协议

4. 注册协议处理器

在SpringBoot的启动流程里我们专门有提到过Handlers,它就是用来注册协议处理器的在使用3.3.3的代码之前,执行如下代码,就能执行成功了。

Handlers.register();

Handlers.register()实际是通过包名: org.springframework.boot.loader.net.protocol,注册了jar、nested(包名)这两个协议的处理器Handler(对于包下固定类名)

5. 启动SpringBoot

现在我们已经能完整的加载整个SpringBoot可以执行jar了,我们是不是能像JarLauncher一样调用Start-Class的main方法来启动应用呢?答案是肯定的。

private static void testLaunched(URL url) throws Exception {
    URLClassLoader ucl = new URLClassLoader("MyURLClassLoader", new URL[]{url}, null);
    Class<?> clazz = ucl.loadClass("org.springframework.boot.loader.JarLauncher");
    Archive archive = Archive.create(clazz);
    Set<URL> su = archive.getClassPathUrls((Archive.Entry e) -> true);

    Handlers.register();
    su.add(url);

    LaunchedClassLoader lcl = new LaunchedClassLoader(false, su.toArray(URL[]::new), null);

    clazz = Class.forName("com.keyniu.yangsi.YangsiApplication", false, lcl);               // 启动类
    Method mainMethod = clazz.getDeclaredMethod("main", String[].class);
                    // main方法
    mainMethod.setAccessible(true);

    mainMethod.invoke(null, (Object) new String[0]);                                        // 调用main方法
}

5. 参考资料

  1. JVM Specification: Loading、Linking And Initializing,Chapter 5. Loading, Linking, and Initializing
  2. SClass Loaders in Java | Baeldung
  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot启动流程大致如下: 1. 创建 SpringApplication 对象:SpringApplication 是 Spring Boot 的核心,负责启动应用程序并装载 Spring 上下文。在创建 SpringApplication 对象时,会传入一个主配置,用于指定 Spring Boot 应用的入口配置。 2. 加载应用配置:SpringApplication 会读取应用的配置文件(如 application.properties 或 application.yml),并将配置信息加载Spring 环境中。 3. 创建 Spring 环境:SpringApplication 根据配置文件中的属性,创建一个新的 Spring 环境对象(Environment),该对象负责管理配置属性和激活的 profile。 4. 准备应用上下文:SpringApplication 根据配置创建一个空的应用上下文(ApplicationContext)对象。 5. 预处理上下文:该步骤涉及许多上下文初始化的准备工作,例如设置环境、加载器、属性源等。 6. 加载应用上下文:使用 Spring装载机制(Loaders)加载一系列 BeanDefinition 到应用上下文中。 7. 刷新应用上下文:应用上下文准备就绪后,调用 refresh() 方法进行刷新。在这个过程中,Spring启动各种事件、监听器等机制,并完成 Bean 的实例化、依赖注入等操作。 8. 启动应用:刷新完成后,调用 SpringApplication 的 run() 方法启动应用。该方法会执行一些准备工作,然后调用 Tomcat 或其他嵌入式容器来启动 Web 服务。 9. 应用运行:应用运行起来后,会开始处理请求,并将结果返回给客户端。同时,Spring Boot 提供了许多自动配置和约定大于配置的功能,简化了开发过程。 总结起来,Spring Boot启动流程可以分为初始化环境、加载配置、创建上下文、刷新上下文和启动应用等阶段。这些步骤使得开发者可以快速构建和运行 Spring Boot 应用程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值