[Spring源码] 浅析 SpringApplication`的构造方法

文章目录

    • `SpringApplication`的构造方法
      • 获取 Bean Definition 源
      • 推断应用类型
      • 添加 ApplicationContext 初始化器
      • 添加事件监听器
      • 主类推断

SpringApplication的构造方法

Springboot的主启动类为:

@SpringBootApplication
public class BootApplication {

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

}

其中 SpringApplication#run() 方法 调用的是如下静态方法:

	/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified source using default settings.
	 * @param primarySource the primary source to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

	/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified sources using default settings and user supplied arguments.
	 * @param primarySources the primary sources to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

最终使用 new 关键字构造了 SpringApplication 对象,然后调用了非静态 run() 方法。


	/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		Startup startup = Startup.create();
		if (this.registerShutdownHook) {
			SpringApplication.shutdownHook.enableShutdownHookAddition();
		}
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			startup.started();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
			}
			listeners.started(context, startup.timeTakenToStarted());
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			...
		}
		try {
			if (context.isRunning()) {
				listeners.ready(context, startup.ready());
			}
		}
		catch (Throwable ex) {
			...
		}
		return context;
	}

构造 SpringApplication 对象时做了如下几件事:

  1. 获取 Bean Definition 源
  2. 推断应用类型
  3. 添加 ApplicationContext 初始化器
  4. 添加事件监听器
  5. 主类推断

获取 Bean Definition 源

package com.example.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

import java.util.Arrays;
import java.util.Collections;

@SpringBootApplication
public class BootApplication {

    public static void main(String[] args) {
        SpringApplication spring = new SpringApplication(BootApplication.class);

        // 设置 xml 的bean
        spring.setSources(Collections.singleton("classpath:bean.xml"));

        // 创建并初始化 Spring 容器
        ConfigurableApplicationContext context = spring.run(args);
        Arrays.stream(context.getBeanDefinitionNames()).forEach(i -> {
            System.out.println("name: " + i +
                " 来源: " + context.getBeanFactory().getBeanDefinition(i).getResourceDescription());
        });
        context.close();
    }

    static class Bean1 {
    }

    static class Bean2 {
    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }


}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
						http://www.springframework.org/schema/util
						http://www.springframework.org/schema/util/spring-util-4.0.xsd"
>
    <bean id="bean1" class="com.example.boot.BootApplication.Bean1" />
</beans>


输出

...
name: bootApplication 来源: null
name: bean1 来源: class path resource [bean.xml]
name: bean2 来源: com.example.boot.BootApplication

...

其中来源为 null的是 Spring内置的。

推断应用类型

应用推断主要在 SpringbootApplication的构造方法中,this.webApplicationType = WebApplicationType.deduceFromClasspath();

    static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) 
            && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) 
            && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
            return REACTIVE;
        } else {
            String[] var0 = SERVLET_INDICATOR_CLASSES;
            int var1 = var0.length;

            for(int var2 = 0; var2 < var1; ++var2) {
                String className = var0[var2];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    // 非 web 
                    return NONE;
                }
            }
        	// servlet
            return SERVLET;
        }
    }

当然,我们可以直接使用反射调用这个静态方法,判断当前应用环境

        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        System.out.println("\t应用类型为: " + deduceFromClasspath.invoke(null));

输出

应用类型为: SERVLET

添加 ApplicationContext 初始化器

调用 SpringApplication 对象的 run() 方法时会创建 ApplicationContext,最后调用 ApplicationContext 的 refresh() 方法完成初始化。

在创建与初始化完成之间的一些拓展功能就由 ApplicationContext 初始化器完成。

SpringApplication 的构造方法中,添加的初始化器信息从配置文件中读取:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
           ...
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	 初始化器
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

当然可以调用 SpringApplication 对象的 addInitializers() 方法添加自定义初始化器:

注意添加初始化器需要在调用 run方法之前,因为 run 方法会 refresh

        // 初始化器
        // 这里用到了函数方法,可以简化代码
        spring.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println(">>>>>>>>>>");
                if( applicationContext instanceof GenericApplicationContext genericApplicationContext) {
                    System.out.println(">>>>> 注册 bean3");
                    genericApplicationContext.registerBean("bean3", Bean3.class);
                }
            }
        });

        // 创建并初始化 Spring 容器
        ConfigurableApplicationContext context = spring.run(args);

输出

name: bean3 来源: null
name: bean1 来源: class path resource [bean.xml]
name: bean2 来源: com.example.boot.BootApplication
name: beanNameViewResolver 来源: class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]
name: beanNameHandlerMapping 来源: class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
	应用类型为: SERVLET

添加事件监听器

与添加 ApplicationContext 初始化器一样,在 SpringApplication 的构造方法中,添加的事件监听器信息从配置文件中读取:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
           ...
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	 初始化器
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));

        // 事件监听器
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

可以调用 SpringApplication 对象的 addListeners() 方法添加自定义事件监听器:

        spring.addListeners(event -> System.out.println("\t事件为: " + event));

        // 创建并初始化 Spring 容器
        ConfigurableApplicationContext context = spring.run(args);

输出

2023-12-12 23:08:04.067  INFO 82643 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
	事件为: org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@54db056b]
	事件为: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]
2023-12-12 23:08:04.073  INFO 82643 --- [           main] com.example.boot.BootApplication         : Started BootApplication in 0.695 seconds (JVM running for 0.9)
	事件为: org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@5119fb47]
	事件为: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]
	事件为: org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@5119fb47]
	事件为: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]
	事件为: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]
	事件为: org.springframework.context.event.ContextClosedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]

主类推断

依然是在 SpringApplication的构造方法中,有

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
           ...
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	 初始化器
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));

        // 事件监听器
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        // 主类推断
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }
    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
            StackTraceElement[] var2 = stackTrace;
            int var3 = stackTrace.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                StackTraceElement stackTraceElement = var2[var4];
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException var6) {
        }

        return null;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/240272.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高通平台开发系列讲解(USB篇)Composite USB gadget framework

文章目录 一、Gadget framework二、Composite driver and gadget driver interaction沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要图解高通平台PCIe EP软件架构 一、Gadget framework Composite USB gadget framework 架构如下所示: The composite fram…

<软考>软件设计师-4知识产权与标准化(总结)

(一)知识产权概述 1 知识产权 是指公民、法人、非法人单位对自己的创造性智力成果和其他科技成果依法享有的民事权。是智力成果的创造人依法享有的权利和在生产经营活动中标记所有人依法所享有的权利的总称。包含著作权、专利权、商标权、商业秘密权、植物新品种权、集成电路布…

Network 灰鸽宝典【目录】

目前已有文章 21 篇 Network 灰鸽宝典专栏主要关注服务器的配置&#xff0c;前后端开发环境的配置&#xff0c;编辑器的配置&#xff0c;网络服务的配置&#xff0c;网络命令的应用与配置&#xff0c;windows常见问题的解决等。 文章目录 服务配置环境部署GitNPM 浏览器编辑器系…

玻色量子袁为出席中国移动第四届科技周量子计算算法与应用分论坛

9月12日&#xff0c;中国移动第四届科技周“量子计算算法与应用”分论坛在北京成功举办&#xff0c;中国移动研究院院长黄宇红发表致辞&#xff0c;中国移动未来研究院院长崔春风全程主持。玻色量子作为光量子计算领域真机测试与场景应用的标杆企业应邀出席&#xff0c;玻色量子…

Kubernetes(k8s)集群部署----->超详细

Kubernetes&#xff08;k8s&#xff09;集群部署-----&#xff1e;超详细 一、资源准备二、安装准备2.1 主机环境设置2.1.1 关闭操作系统防火墙、selinux2.1.2 关闭swap交换分区2.1.3 允许iptables检测桥接流量&#xff08;可选&#xff09; 2.2 安装Docker环境2.3 安装Kubeadm…

自动化补丁管理软件

什么是自动化补丁管理 自动补丁管理&#xff08;或自动补丁&#xff09;是指整个补丁管理过程的自动化&#xff0c;从扫描网络中的所有系统到检测缺失的补丁&#xff0c;在一组测试系统上测试补丁&#xff0c;将它们部署到所需的系统&#xff0c;并提供定期更新和补丁部署状态…

不再兼容“安卓“,鸿蒙开发与android对比

首先&#xff0c;鸿蒙系统采用了分布式技术&#xff0c;其设计理念是“能用分布式解决的问题就不用单机解决”。这意味着鸿蒙旨在构建一个统一的分布式操作系统&#xff0c;可以支持不同设备之间的交互和通信。 而安卓系统基于Linux内核和Java编程语言构建&#xff0c;属于单机…

代理ip一般适用于什么行业,什么场景

代理IP适用于多个行业和场景&#xff0c;以下是其中一些主要的应用领域&#xff1a; 互联网营销&#xff1a; 数据抓取&#xff1a;用于收集竞争对手的价格、产品信息等。社交媒体管理&#xff1a;在不同账户之间切换&#xff0c;提高账号安全性或进行市场调研。广告投放优化&a…

探索SSL证书的应用场景,远不止网站,还有小程序、App Store等

说到SSL证书&#xff0c;我们都知道其是用于实现HTTPS加密保障数据安全的重要工具&#xff0c;在建设网站的时候经常会部署SSL证书。但实际上&#xff0c;SSL证书的应用场景远不止网站&#xff0c;它还被广泛地应用到小程序、App Store、抖音广告、邮件服务器以及各种物联网设备…

西南交通大学【数电实验6---可控分频器设计】

一、实验电路图、状态图、程序代码、仿真代码、仿真波形图&#xff08;可以只写出核心功能代码&#xff0c;代码要有注释&#xff09; 不管sel为0或者1&#xff0c;clk_out[0]的频率都是不变的&#xff0c;故在always块当中&#xff0c;可优先对clk_out[0]进行处理&#xff0c;…

文件操作及函数

什么是文件&#xff1f; 在程序设计中&#xff0c;文件有两种&#xff1a;程序文件和数据文件。 程序文件 包括源程序文件&#xff08;.c&#xff09;&#xff0c;目标文件&#xff08;.obj&#xff09;&#xff0c;可执行程序(.exe)。 数据文件 文件的内容不一定是程序&…

超过 1450 个 pfSense 服务器因错误链而遭受 RCE 攻击

在线暴露的大约 1450 个 pfSense 实例容易受到命令注入和跨站点脚本漏洞的攻击&#xff0c;这些漏洞如果链接起来&#xff0c;可能使攻击者能够在设备上执行远程代码。 pfSense 是一款流行的开源防火墙和路由器软件&#xff0c;允许广泛的定制和部署灵活性。 它是一种经济高效…

MYSQL练题笔记-子查询-电影评分

一、题目相关内容 1&#xff09;相关的表 2&#xff09;题目 3&#xff09;帮助理解题目的示例&#xff0c;提供返回结果的格式 二、自己初步的理解 1.字典序是指从前到后比较两个字符串大小的方法。 首先比较第1个字符&#xff0c;如果不同则第1个字符较小的字符串更小&…

在idea中使用maven创建dynamic web project

0、先正确安装MAVEN, TOMCAT &#xff0c;并集成到idea 1、new 一个 project&#xff0c; 使用maven的archetype-webapp创建 2、等待创建&#xff0c;会提示build success 3、给project 添加tomcat配置&#xff0c;并部署project到 tomcat 4、运行 5、OK 6、再次引入时&…

CentOS 7 源码部署 Nginx

文章目录 1. 概述2. 部署示例2.1 下载和解压 Nginx 源码2.2 安装编译依赖包2.3 编译和安装2.4 启动 Nginx2.5 配置防火墙2.6 设置 Nginx 为系统服务2.7 配置访问 3. 扩展知识 1. 概述 Nginx 是一款高性能的开源 Web 服务器软件&#xff0c;广泛应用于互联网领域。本篇博客将介…

go学习之反射知识

反射 文章目录 反射1、反射的使用场景1&#xff09;结构体标签的应用2&#xff09;使用反射机制编写函数的适配器&#xff08;桥连接&#xff09; 2、反射的基本介绍-1.基本介绍-2.反射的图解-3.反射重要的函数和概念 3.反射快速入门-1.请编写一个函数&#xff0c;演示对&#…

设计模式-享元模式

设计模式专栏 模式介绍模式特点应用场景工厂模式和享元模式的区别代码示例Java实现享元模式python实现享元模式 享元模式在spring中的应用 模式介绍 享元模式是一种软件设计模式&#xff0c;它使用共享对象来减少内存使用量&#xff0c;并分享信息给尽可能多的相似对象。这种模…

【C语言程序设计】循环结构程序设计

目录 前言 一、程序设计第一题 二、程序设计第二题 三、程序设计第三题 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da…

C语言—每日选择题—Day45

第一题 1. 以下选项中&#xff0c;对基本类型相同的指针变量不能进行运算的运算符是&#xff08;&#xff09; A&#xff1a; B&#xff1a;- C&#xff1a; D&#xff1a; 答案及解析 A A&#xff1a;错误&#xff0c;指针不可以相加&#xff0c;因为指针相加可能发生越界&…

Geotrust中的dv ssl证书

DV SSL数字证书是入门级的数字证书&#xff0c;Geotrust的子品牌RapidSSL旗下的SSL数字证书产品都是入门级的SSL数字证书——DV基础型单域名SSL证书和DV基础型通配符SSL证书。今天就随SSL盾小编了解Geotrust旗下的DV SSL证书。 1.Geotrust旗下的DV基础型单域名SSL证书能够保护…