⚙️SpringBoot 源码解析:深度剖析 SpringApplication 构造方法

举报
bug菌 发表于 2024/11/28 23:25:35 2024/11/28
【摘要】 前言 🚀各位亲爱的开发者朋友们,今天我们继续深入探讨 SpringApplication 这一 Spring Boot 启动的核心组件。上次我们从整体上看了 SpringApplication.run() 的执行过程,今天则要聚焦于 SpringApplication 的构造方法,带你逐行解密它是如何将应用从启动类传递到 Spring 容器,再到你面前一切正常运行的状态。你可能会想:“构...

前言 🚀

各位亲爱的开发者朋友们,今天我们继续深入探讨 SpringApplication 这一 Spring Boot 启动的核心组件。上次我们从整体上看了 SpringApplication.run() 的执行过程,今天则要聚焦于 SpringApplication 的构造方法,带你逐行解密它是如何将应用从启动类传递到 Spring 容器,再到你面前一切正常运行的状态。

你可能会想:“构造方法不就传个参数,做些简单的初始化吗?”错了!这可不是普通的构造方法,它涉及了 Spring Boot 启动过程中的一系列重要配置,自动化的核心就在这里!接下来,我们一起深入源码,看看这背后到底隐藏了多少精彩的细节。🧐

🔧 SpringApplication 构造方法的细节剖析

1. 构造方法的核心:primarySources 和 sources

首先,来看看 SpringApplication 的构造方法,它接收的第一个参数是一个或多个启动类(primarySources)。这是构造方法中的关键部分,决定了应用的核心配置和初始化内容。

public SpringApplication(Class<?>... primarySources) {
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.sources = new LinkedHashSet<>(Arrays.asList(primarySources));
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
}

这一段代码看起来很简单,但它做的事可不小!首先,primarySources 就是你传入的启动类,它们通常会被注解为 @SpringBootApplication。Spring Boot 使用这些类来进行自动配置,识别应用的配置源。这就是构造方法的第一个关键点。

接着,这些类会被存储在一个 LinkedHashSet 中,这样做的目的是保留启动类的顺序,这对于后续的初始化器和监听器加载来说至关重要。

2. setInitializers:加载初始化器

接下来,我们进入一个非常有趣的部分——初始化器(Initializers)。setInitializers() 方法负责加载所有的 ApplicationContextInitializer 实例,初始化器的任务就是在 Spring 容器初始化之前对环境进行一些定制化的操作。这意味着你可以通过这些初始化器,修改 Spring 环境、调整容器配置,或者执行一些额外的准备工作。

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

通过 getSpringFactoriesInstances() 方法,Spring Boot 会从 spring.factories 文件中加载实现了 ApplicationContextInitializer 接口的类,这些类会在 Spring 容器启动之前自动执行。这一机制让你能够以非常灵活的方式定制 Spring 容器的行为,不再需要每次都手动配置繁琐的步骤。

3. setListeners:加载监听器

setListeners() 方法是 SpringApplication 构造方法中的另一大亮点。它负责加载所有的 ApplicationListener 实例。你可能会想,监听器和初始化器有什么区别?其实很简单:初始化器是用来在应用启动前做一些调整的,而监听器则是在应用生命周期的不同阶段(如应用上下文启动、刷新等)触发特定事件时,做出响应。

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

通过这个方法,Spring Boot 会从 spring.factories 中加载实现了 ApplicationListener 接口的类。监听器为我们提供了一个在应用启动过程中执行自定义操作的机会。你可以在这些监听器中处理像应用上下文启动、刷新和关闭等事件。

4. 如何加载 SpringFactories 实例?

说到 getSpringFactoriesInstances(),它的作用是从 spring.factories 文件中加载配置项。Spring Boot 通过这种方式来动态加载和注册初始化器和监听器,让我们的启动过程更加灵活。这个方法通过 SpringFactoriesLoader 来实现,SpringFactoriesLoader 会根据提供的类型,加载对应的实现类。这个机制让 Spring Boot 可以根据不同的需求灵活加载各类配置和扩展。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return SpringFactoriesLoader.loadFactories(type, getClass().getClassLoader());
}

这一段代码的作用是,通过类加载器加载 spring.factories 文件中的所有实例,保证了 Spring Boot 启动过程中的动态配置和自动化。

5. 小结:SpringApplication 构造方法的深度定制

通过上面的剖析,我们可以看到,SpringApplication 的构造方法并不仅仅是简单的赋值操作。它通过加载启动类、初始化器、监听器等多个组件,完成了对应用上下文和环境的配置。这些机制使得 Spring Boot 能够在启动时动态加载配置,最大程度地实现自动化配置。

Spring Boot 之所以如此受欢迎,正是因为它让复杂的配置变得简单,通过这种“构造方法 + 配置”模式,开发者可以专注于业务逻辑的开发,而不必过多关注框架和容器的细节。

6.示例演示

为了帮助大家更好地理解 SpringApplication 构造方法的具体实现,下面我将通过一些实际代码示例来展示每个部分是如何工作的。

1. 启动类(primarySources)

首先,我们来看 SpringApplication 是如何处理启动类 primarySources 的。假设我们有一个简单的 Spring Boot 应用,它的启动类如下:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

这里的 Application 类就是我们的启动类,它被传递给 SpringApplication.run() 方法,最终进入 SpringApplication 的构造方法。primarySources 就是这里传入的 Application.class,表示我们希望从 Application 类开始初始化 Spring Boot 应用。

2. setInitializers:加载初始化器

在 Spring Boot 启动过程中,我们可能需要在应用上下文初始化之前执行一些自定义操作。ApplicationContextInitializer 就是实现这个需求的一个接口。你可以创建自己的初始化器并通过 SpringApplication 加载。

比如,我们创建一个简单的初始化器,它会在 Spring 容器启动之前打印一些日志:

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("Spring Boot 应用启动前,初始化容器...");
    }
}

然后,在 SpringApplication 中注册这个初始化器:

public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setInitializers(Collections.singletonList(new MyApplicationContextInitializer()));
        app.run(args);
    }
}

这时,当你启动应用时,MyApplicationContextInitializer 会在 Spring 容器初始化前被执行,输出 Spring Boot 应用启动前,初始化容器...

3. setListeners:加载监听器

监听器的作用是在应用生命周期的不同阶段响应事件。你可以通过 ApplicationListener 实现对特定事件的监听。例如,监听应用启动成功事件:

public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("Spring Boot 应用启动完成!");
    }
}

然后,像初始化器一样,我们可以将监听器注册到 SpringApplication 中:

public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setListeners(Collections.singletonList(new MyApplicationListener()));
        app.run(args);
    }
}

在这个例子中,当 Spring Boot 应用启动完成后,控制台会输出 Spring Boot 应用启动完成!

4. 动态加载 SpringFactories 实例

Spring Boot 利用 spring.factories 文件来动态加载初始化器和监听器。这些工厂配置文件通常位于 src/main/resources/META-INF/spring.factories 路径下。

假设我们想要在启动时自动注册一个监听器,首先创建一个 spring.factories 文件:

# src/main/resources/META-INF/spring.factories
org.springframework.context.ApplicationListener=com.example.MyApplicationListener

然后,在 SpringApplication 中,它会通过 SpringFactoriesLoader 自动加载这个文件并将相关的监听器添加到应用中:

public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        // Spring Boot 会自动加载 spring.factories 中的配置
        app.run(args);
    }
}

在这种方式下,你不需要显式地通过代码注册监听器,Spring Boot 会根据 spring.factories 中的配置自动加载。

5. 完整示例

将所有部分结合在一起,下面是一个完整的示例,演示了如何通过 SpringApplication 配置启动类、初始化器、监听器以及如何利用 spring.factories 动态加载配置:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        
        // 手动添加自定义初始化器
        app.setInitializers(Collections.singletonList(new MyApplicationContextInitializer()));
        
        // 手动添加自定义监听器
        app.setListeners(Collections.singletonList(new MyApplicationListener()));
        
        // 运行 Spring Boot 应用
        app.run(args);
    }
}

// 自定义的 ApplicationContextInitializer
class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("Spring Boot 应用启动前,初始化容器...");
    }
}

// 自定义的 ApplicationListener
class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("Spring Boot 应用启动完成!");
    }
}

通过这些代码,我们可以清楚地看到 SpringApplication 如何在启动时加载和执行初始化器、监听器,以及如何使用 spring.factories 机制动态加载配置。这样,你就可以灵活地定制 Spring Boot 应用的启动过程了!🎉

代码解析:

在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

在这个示例中,您展示了如何通过手动设置 ApplicationContextInitializerApplicationListener 来扩展 Spring Boot 应用的启动过程。
Application 类:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        
        // 手动添加自定义初始化器
        app.setInitializers(Collections.singletonList(new MyApplicationContextInitializer()));
        
        // 手动添加自定义监听器
        app.setListeners(Collections.singletonList(new MyApplicationListener()));
        
        // 运行 Spring Boot 应用
        app.run(args);
    }
}
  1. SpringApplication 实例化:您通过 new SpringApplication(Application.class) 创建了一个 SpringApplication 对象。
  2. 添加自定义初始化器:使用 app.setInitializers 方法手动添加了一个自定义的 ApplicationContextInitializer,它会在 Spring 应用上下文刷新之前执行。
  3. 添加自定义监听器:使用 app.setListeners 方法添加了一个自定义的 ApplicationListener,它会在 Spring Boot 应用启动完成后触发事件。

自定义 ApplicationContextInitializer

class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("Spring Boot 应用启动前,初始化容器...");
    }
}
  • ApplicationContextInitializer 是 Spring Boot 在启动时的一个接口,允许您在 Spring 容器刷新之前进行一些初始化操作。
  • initialize 方法会在应用上下文刷新前被调用,您可以在这里执行一些额外的逻辑,例如设置特定的属性、初始化资源等。

自定义 ApplicationListener

class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("Spring Boot 应用启动完成!");
    }
}
  • ApplicationListener 是一个监听 Spring 事件的接口。在这个例子中,您监听的是 ApplicationReadyEvent 事件,这是 Spring Boot 应用启动完成之后发布的事件。
  • onApplicationEvent 方法会在应用启动完成时被调用,可以在这里执行与应用启动相关的操作。

关键点:

  1. ApplicationContextInitializer:用于在应用上下文刷新之前进行初始化操作,通常用于修改容器的设置或初始化外部资源。
  2. ApplicationListener:用于监听 Spring 应用启动过程中的事件。在这个例子中,我们监听的是 ApplicationReadyEvent,即应用启动完成后的事件。
  3. SpringApplication.run():这是 Spring Boot 启动应用的入口方法,调用后会触发 Spring Boot 的启动过程。

运行过程:

  1. 初始化过程
    • SpringApplication 运行之前,MyApplicationContextInitializerinitialize() 方法会被调用,输出 “Spring Boot 应用启动前,初始化容器…”。
  2. 应用启动
    • SpringApplication.run() 会启动 Spring Boot 应用并初始化 Spring 容器。
  3. 启动完成
    • 当应用完全启动并准备好提供服务时,MyApplicationListeneronApplicationEvent() 方法会被调用,输出 “Spring Boot 应用启动完成!”。

应用场景:

  • ApplicationContextInitializer:常用于在 Spring Boot 应用上下文加载之前做一些容器的初始化,比如注册自定义的 Bean、修改容器配置等。
  • ApplicationListener:用于监听 Spring Boot 应用生命周期中的特定事件,如应用启动完成、环境准备好等。可以在应用启动完成时执行一些初始化逻辑,或者向外部系统报告应用状态。

🧳 总结:魔法背后,SpringBoot 的“构造秘密”

今天的源码解析,让我们深入了解了 SpringApplication 构造方法的方方面面。从 primarySourcessetInitializerssetListeners,每一步都在为我们的应用启动做好充分的准备。这一切都是为了让 Spring Boot 在启动时能够自动化地完成大量的配置和初始化工作,从而大大减少了开发者的工作量。

如果你觉得今天的内容有帮助,别忘了点赞和分享!同时,如果你对 Spring Boot 有更多的疑问或者想进一步探索的地方,随时欢迎来和我一起讨论!🔥

🧧福利赠与你🧧

  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。

最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。

✨️ Who am I?

我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。