• Spring Boot 2.x源码系列【5】启动流程深入解析之准备环境


    有道无术,术尚可求,有术无道,止于术。

    本系列Spring Boot版本2.7.0

    前言

    在上文中,我们分析到启动刚开始阶段,会去调用监听器进行一些初始化的操作,接下来进入到环境准备阶段:

    			// 封装参数
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                // 准备环境
                ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
                // 配置 beaninfo
                this.configureIgnoreBeanInfo(environment);
                // 打印 Banner 
                Banner printedBanner = this.printBanner(environment);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    核心类和接口

    ApplicationArguments

    ApplicationArguments翻译过来是应用程序参数的意思,它的主要作用是提供对用于运行 SpringApplication 的参数的访问,也就是封装了我们在使用run方式时传入的参数,以便其他阶段访问使用。

    SpringApplication.run(App1.class,args);
    
    • 1

    该接口的源码如下:

    public interface ApplicationArguments {
    	// 返回传递给应用程序的原始未处理参数
    	String[] getSourceArgs();
    	// 返回所有选项参数的名称。
    	Set<String> getOptionNames();
    	// 返回从参数解析的选项参数集是否包含具有给定名称的选项。
    	boolean containsOption(String name);
    	// 返回与具有给定名称的参数选项关联的值的集合。
    	List<String> getOptionValues(String name);
        // 返回已解析的非选项参数的集合。
    	List<String> getNonOptionArgs();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    它只有一个默认的实现类DefaultApplicationArguments

    ConfigurableEnvironment

    ConfigurableEnvironment是Spring 提供的接口,并继承了多个接口:
    在这里插入图片描述
    首先简单说明下它的父类接口的作用:

    • PropertyResolver:属性解析器,用于针对任何底层源(比如文件)解析属性的接口,定义了一系列读取,解析,判断是否包含指定属性的方法,比如可以从YML、系统参数等加载属性。
      • ConfigurablePropertyResolver:可配置的属性解析器,定义用于访问和自定义将属性值从一种类型转换为另一种类型时功能,也就是比PropertyResolver多了一个类型转换。
    • Environment:表示当前应用程序运行环境的接口。对应用程序环境的两个关键方面进行建模:properties配置文件(应用程序配置文件)和profiles(激活环境命名空间)。

    所以ConfigurableEnvironment 具备了属性解析器和当前应用环境的能力,而且在这个基础上扩展了很多自己的方法:

    public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
    	// 设置当前激活的 profile
    	void setActiveProfiles(String... profiles);
    	// 添加 profile
    	void addActiveProfile(String profile);
    	// 设置默认的 profile
    	void setDefaultProfiles(String... profiles);
    	// 获取PropertySource键值组合的集合
    	MutablePropertySources getPropertySources();
    	// 获取系统属性
    	Map<String, Object> getSystemProperties();
    	//	获取系统环境变量
    	Map<String, Object> getSystemEnvironment();
    	// 合并其他 ConfigurableEnvironment 
    	void merge(ConfigurableEnvironment parent);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    该接口的实现类中,可以看到根据不同类型的Web 应用创建不同的环境:
    在这里插入图片描述

    PropertySource

    PropertySource翻译过来是属性源的意思,属性源就是封装了属性的任何类型对象,比如:

    • java.util.Properties 对象
    • java.util.Map 对象
    • ServletContext 和 ServletConfig 对象(用于访问 init 参数)
    • YML配置文件

    EnvironmentPostProcessor

    EnvironmentPostProcessor 接口也是Spring Boot 自己提供的接口,他的作用是在刷新应用程序上下文之前对Environment进行后置处理,它的实现类必须在 META-INFspring.factories文件中注册。

    可以看到它只有一个方法,我们可以通过该方法,对ConfigurableEnvironment 进行自定义处理,比如加载自定义属性源。

    @FunctionalInterface
    public interface EnvironmentPostProcessor {
    	// 后置处理
    	void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1. 封装参数

    这一步很简单,就是将run方式时传入的参数封装为ApplicationArguments对象

     ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    
    • 1

    2. 创建环境

    prepareEnvironment准备环境方法中,会完成环境的创建、配置:

        private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        	// 1. 创建环境
            ConfigurableEnvironment environment = this.getOrCreateEnvironment();
            // 2. 配置环境
            this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
            // 3. 连接
            ConfigurationPropertySources.attach((Environment)environment);
            // 4. 调用监听器
            listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
           	// 5. 将 defaultProperties 移到最后位置
            DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
            Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
            this.bindToSpringApplication((ConfigurableEnvironment)environment);
            // 6. 没有设置 isCustomEnvironment 属性,
            if (!this.isCustomEnvironment) {
            	// 环境转换器
                EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());
                // 进行转换,这里实际已经是 Servlet 的类型的,所以不转换直接返回
                environment = environmentConverter.convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
            }
    		// 7. 再次连接并返回
            ConfigurationPropertySources.attach((Environment)environment);
            return (ConfigurableEnvironment)environment;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    准备环境阶段,第一步就是根据Web 应用的类型创建环境对象:

        private ConfigurableEnvironment getOrCreateEnvironment() {
        	// 1. 环境已初始化,直接返回
            if (this.environment != null) {
                return this.environment;
            } else {
            	// 2. 判断环境的类型
                switch(this.webApplicationType) {
                // 3. SERVLET类型=》ApplicationServletEnvironment
                case SERVLET:
                    return new ApplicationServletEnvironment();
                    // REACTIVE类型=》ApplicationReactiveWebEnvironment
                case REACTIVE:
                    return new ApplicationReactiveWebEnvironment();
                default:
                // 默认=》ApplicationEnvironment
                    return new ApplicationEnvironment();
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    SpringApplication对象创建时,会去判断当前应用Web类型:

        this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    • 1

    判断的逻辑是查看Classpath下是否有对应的类,因为这里使用的是Spring MVC,所以最后返回的是ApplicationServletEnvironment

    	static WebApplicationType deduceFromClasspath() {
    		// org.springframework.web.reactive.DispatcherHandler
    		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    			return WebApplicationType.REACTIVE;
    		}
    		// javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext
    		for (String className : SERVLET_INDICATOR_CLASSES) {
    			if (!ClassUtils.isPresent(className, null)) {
    				return WebApplicationType.NONE;
    			}
    		}
    		// 都不是,判断是 SERVLET
    		return WebApplicationType.SERVLET;
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    new ApplicationServletEnvironment()直接调用无参构造函数,实际是调用父类的无参构造函数:

        public AbstractEnvironment() {
        	// 创建一个PropertySources 对象,用于存放所有的 PropertySource
            this(new MutablePropertySources());
        }
    
        protected AbstractEnvironment(MutablePropertySources propertySources) {
            this.logger = LogFactory.getLog(this.getClass());
            this.activeProfiles = new LinkedHashSet(); // 激活的 Profile 
            this.defaultProfiles = new LinkedHashSet(this.getReservedDefaultProfiles()); // 默认的Profile =》 default
            this.propertySources = propertySources;
            // 创建属性解析器=》PropertySourcesPropertyResolver
            this.propertyResolver = this.createPropertyResolver(propertySources);
            // 自定义属性源
            this.customizePropertySources(propertySources);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    customizePropertySources可以看到创建了三个属性源,名称分别为:

    • servletConfigInitParams:Servlet 上下文初始化参数属性源名称:“servletContextInitParams”。
    • servletContextInitParams:Servlet 配置初始化参数属性源名称:“servletConfigInitParams”。
    • jndiProperties:JNDI 属性源名称:“jndiProperties”。
        protected void customizePropertySources(MutablePropertySources propertySources) {
            propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
            propertySources.addLast(new StubPropertySource("servletContextInitParams"));
            if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
                propertySources.addLast(new JndiPropertySource("jndiProperties"));
            }
            super.customizePropertySources(propertySources);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    紧接着进入到customizePropertySources(propertySources)方法,也创建了systemPropertiessystemEnvironment属性源。

        protected void customizePropertySources(MutablePropertySources propertySources) {
        	// 添加System 系统属性
            propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
            // 添加系统环境属性
            propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最终这一步创建的ConfigurableEnvironment对象中,一共有4个PropertySource,其中前两个是没有放任何东西的:
    在这里插入图片描述
    systemProperties中,是将System类中的属性获取过来并封装,可以看到这里主要是JDK 相关的属性:
    在这里插入图片描述

    systemEnvironment主要是当前操作系统的很多属性:
    在这里插入图片描述

    3. 配置环境

    接着进入到配置环境方法中,会对属性、Profile 进行配置。

        protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        	// 设置转换服务
            if (this.addConversionService) {
                environment.setConversionService(new ApplicationConversionService());
            }
    		// 配置属性
            this.configurePropertySources(environment, args);
            // 配置 Profile 
            this.configureProfiles(environment, args);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    configurePropertySources方法主要是将启动命令中的参数和run 方法中的参数封装为PropertySource

        protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        	// 获取所有的属性源
            MutablePropertySources sources = environment.getPropertySources();
            if (!CollectionUtils.isEmpty(this.defaultProperties)) {
                DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
            }
    		// 是否添加启动命令参数,默认True
            if (this.addCommandLineProperties && args.length > 0) {
                String name = "commandLineArgs";
                if (sources.contains(name)) {
                    PropertySource<?> source = sources.get(name);
                    CompositePropertySource composite = new CompositePropertySource(name);
                    composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                    composite.addPropertySource(source);
                    sources.replace(name, composite);
                } else {
                    sources.addFirst(new SimpleCommandLinePropertySource(args));
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    configureProfiles则是一个空的方法,没有实现,所以配置环境这一步主要也是获取其他方面的属性。

    4. 关联configurationProperties

    attach方法中,主要是将环境中的 PropertySources 封装到一个名称为configurationPropertiesPropertySource中。

        public static void attach(Environment environment) {
            Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
            // 1. 获取环境中的 PropertySources
            MutablePropertySources sources = ((ConfigurableEnvironment)environment).getPropertySources();
            // 2. 判断 是否有configurationProperties,之前加载的四个是没有这个名字的所以为NULL 
            PropertySource<?> attached = getAttached(sources);
            // 3. 创建一个configurationProperties。
            if (attached == null || !isUsingSources((PropertySource)attached, sources)) {
                attached = new ConfigurationPropertySourcesPropertySource("configurationProperties", new SpringConfigurationPropertySources(sources));
            }
    		// 4. 将configurationProperties 设置到第一位置
            sources.remove("configurationProperties");
            sources.addFirst((PropertySource)attached);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5. 调用监听器

    和上一篇开始启动阶段调用监听器一样,在准备环境阶段,也会调用监听器。

    该阶段对应的事件类型为ApplicationEnvironmentPreparedEvent,支持该事件的监听器有5种:
    在这里插入图片描述
    首先进入的是EnvironmentPostProcessorApplicationListener,它的作用是用于触发在 spring.factories文件中声明的EnvironmentPostProcessor实例。

        private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        	// 1. 当前环境和SpringApplication 
            ConfigurableEnvironment environment = event.getEnvironment();
            SpringApplication application = event.getSpringApplication();
            // 2. SPI 获取环境后置处理器,并调用处理
            Iterator var4 = this.getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext()).iterator();
            while(var4.hasNext()) {
            	// 3. 调用后置处理器处理方法
                EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();
                postProcessor.postProcessEnvironment(environment, application);
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    一共有7个 环境后置处理器:
    在这里插入图片描述

    其中最重要的就是ConfigDataEnvironmentPostProcessor,就是用它将application.yml文件中的属性加载到环境中的,加载完成后可以看到你在application.yml文件中配置的所有属性:

    在这里插入图片描述
    其他的环境后置处理器的作用:

    • RandomValuePropertySourceEnvironmentPostProcessor:添加RandomValuePropertySource属性源,可以通过environment.getProperty("random.*")返回各种随机值。
    • SystemEnvironmentPropertySourceEnvironmentPostProcessor:将之前加载的系统属性对象SystemEnvironmentPropertySource替换为一个新的对象OriginAwareSystemEnvironmentPropertySource
    • SpringApplicationJsonEnvironmentPostProcessor: 解析 spring.application.jsonSPRING_APPLICATION_JSON 配置的 json 字符串。
    • CloudFoundryVcapEnvironmentPostProcessor:配置Cloud Foundry开源PaaS云平台相关属性源。
    • DebugAgentEnvironmentPostProcessor:启用 Reactor 调试代理(如果可用)。
    • IntegrationPropertiesEnvironmentPostProcessor:将META-INF/spring.integration.properties中的属性映射为属性源。

    其他监听器在准备环境阶段的作用:

    • AnsiOutputApplicationListener:,Ansi输出应用监听器,该监听器的作用是,当收到应用环境准备就绪事件时,对Ansi输出的相关状态进行设置,并绑定到应用环境中。
    • LoggingApplicationListener: 初始化sb的logging级别,根据环境变量初始化系统,确定最终的logging等级,注册shutdown的钩子方法。
    • BackgroundPreinitializer:后台初始化ConversionServiceInitializer、ValidationInitializer、MessageConverterInitializer、JacksonInitializer、CharsetInitializer组件
    • DelegatingApplicationListener:,通过环境中的配置的context.listener.classes,去搜集相应的监听器。如果收集到,就会创建一个简单事件广播器实例,放到类属性上,同时,还会把收集到的监听器,绑定到该广播器上。
    • FileEncodingApplicationListener:文件编码应用监听器。该监听器实质作用是在收到应用环境准备就绪事件时,对配置中关于文件编码的配置作一个校验,判断配置中的文件编码是否和JVM系统的file.encoding一致。无配置或JVM系统的file.encoding无效的情况下,都不会报异常,但是,当JVM中的file.encoding有效,且在配置中包含了spring.mandatory-file-encoding,而二者又不一致时报IllegalStateException异常。

    6. 绑定

    接着调用bindToSpringApplication,其作用是将spring.main开头的配置信息绑定到SpringApplication类对应的属性中。

        protected void bindToSpringApplication(ConfigurableEnvironment environment) {
            try {
                Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
            } catch (Exception var3) {
                throw new IllegalStateException("Cannot bind to SpringApplication", var3);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    7. 配置beaninfo

    环境创建成功后,进入到configureIgnoreBeanInfo方法,就是配置了一个spring.beaninfo.ignore属性,默认为TRUE,跳过对 BeanInfo 的搜索,这个BeanInfo是JDK 声明的一个接口,可以使用Introspector.getBeanInfo获取一个对象的方法、属性、事件和其他功能的显式信息。

    设置spring.beaninfo.ignore,将使用低级反射和应用标准设计模式自动分析某个类的信息,可以提升一些性能。

        private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
            if (System.getProperty("spring.beaninfo.ignore") == null) {
                Boolean ignore = (Boolean)environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
                System.setProperty("spring.beaninfo.ignore", ignore.toString());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    8. 打印Banner

    环境准备完成后,开始打印Banner :

        private Banner printBanner(ConfigurableEnvironment environment) {
            if (this.bannerMode == Mode.OFF) {
                return null;
            } else {
                ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader((ClassLoader)null);
                // 创建打印对象
                SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);
                // 根据配置模式,进行打印
                return this.bannerMode == Mode.LOG ? bannerPrinter.print(environment, this.mainApplicationClass, logger) : bannerPrinter.print(environment, this.mainApplicationClass, System.out);
            }
        }
        Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
        	// 从环境中获取Banner 对象并打印
            Banner banner = this.getBanner(environment);
            banner.printBanner(environment, sourceClass, out);
            return new SpringApplicationBannerPrinter.PrintedBanner(banner, sourceClass);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    总结

    1. 根据Web 类型创建环境对象,Spring MVC 对应的是ApplicationServletEnvironment
    2. ApplicationServletEnvironment调用父类的构造方法,创建JDK 和操作系统相关的属性源PropertySource;
    3. 调用监听器,进行环境准备阶段的初始化,比如初始化组件、读取应用配置文件等加载各种属性源;
    4. 打印Banner;
    5. 环境创建成功。
  • 相关阅读:
    极智开发 | CUDA Compiler NVCC编译流程
    锁的基础说明
    linux在anaconda环境中配置GPU版本的cuda+cudnn+pytorch深度学习环境(简单可行!一次完成!)
    京东云TiDB SQL优化的最佳实践
    总结谋划明方向 凝心聚力开新局——和数软件对口援疆项目显成效
    MyBatis(中)
    【Mysql】
    LVGL第一阶段
    暑假加餐|有钱人和你想的不一样(第9天)+NSGAⅡ与MOEAD算法Matlab代码
    必备的API接口大全,让你的开发更高效
  • 原文地址:https://blog.csdn.net/qq_43437874/article/details/125482146