springboot源码分析7-环境属性构造过程(上)

使用springboot的目的就是在项目开发中,快速出东西,因此springboot对于配置文件的格式支持是非常丰富的,最常见的配置文件后缀有如下四种:properties、xml、yml、yaml,比如我们在springboot项目根目录中配置了一个application.properties文件,则springboot项目启动的时候就会自动将该文件的内容解析并设置到环境中,这样后续需要使用该文件中配置的属性的时候,只需要使用@value即可。同理application.xml、application.yml、application.yaml文件也会自动被加载并最终设置到环境中。

上面我们提到了环境,那么环境到底是个什么玩意呢?在这里提前说一下:我们这里关注的是源码层面的事情。并非讲解api如何使用。

大家首先思考一下,springboot项目如何启动,这个到很简单,无外乎引入springboot依赖包,设置项目启动的main方法如下所示:

@EnableAutoConfiguration

public class Application {

    private static Logger logger = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {

        long startTime = System.currentTimeMillis();

        SpringApplication.run(Application.class,args);

        logger.info("程序启动花费时间为:" + (System.currentTimeMillis() - startTime) / 1000 + "秒");

    }

}

上述的代码非常的简单,但是springboot做了非常多的事情,因为springboot代码体系非常庞大,所以后续的文章是我们讲解那些源码就直接看那些源码,把不需要了解的暂时放到一边。因此在这里暂时先关注环境的创建源码,我们快速定位到SpringApplication类中的public ConfigurableApplicationContext run(String... args)方法,该方法关于环境的准备代码如下所示:

...

ApplicationArguments applicationArguments = new DefaultApplicationArguments(

args);

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

...

prepareEnvironment方法从名字就可以看出来是准备环境(Environment),prepareEnvironment代码如下:

private ConfigurableEnvironment prepareEnvironment(

SpringApplicationRunListeners listeners,

    ApplicationArguments applicationArguments) {

    //获取或者创建环境

    ConfigurableEnvironment environment = getOrCreateEnvironment();

    //配置环境的信息

    configureEnvironment(environment, applicationArguments.getSourceArgs());

    //通知所有的观察者,环境已经准备好了。

    listeners.environmentPrepared(environment);

    bindToSpringApplication(environment);

if (this.webApplicationType == WebApplicationType.NONE) {

environment = new EnvironmentConverter(getClassLoader())

.convertToStandardEnvironmentIfNecessary(environment);

}

ConfigurationPropertySources.attach(environment);

    return environment;

}

接下来,我们一步步的分析。

1.1. 1.获取或者创建环境

getOrCreateEnvironment()方法如下所示:

 if (this.environment != null) {

    return this.environment;

}

if (this.webApplicationType == WebApplicationType.SERVLET) {

    return new StandardServletEnvironment();

}

return new StandardEnvironment();

上述代码逻辑如下:

1.如果environment不为空则直接返回。

2.如果是web环境则直接实例化StandardServletEnvironment类。

3.如果不是web环境则直接实例化StandardEnvironment类。

1.2. WebApplicationType类型

Springboot2版本开始增加了WebApplicationType的类型,其定义如下:

public enum WebApplicationType {

/**

 * 不需要再web容器的环境下运行,也就是普通的工程

 */

NONE,

/**

    基于servlet的Web项目

 */

SERVLET,

/**

响应式web应用==reactive web Spring5版本的新特性

 */

REACTIVE

}

1.3. ConfigurableEnvironment类

environment 为ConfigurableEnvironment类型。我们不妨看一下该类的层次图如下所示:

 

    Environment接口是Spring对当前程序运行期间的环境的封装(spring)。主要提供了两大功能:profile和property(顶级接口PropertyResolver提供)。目前主要有StandardEnvironment、StandardServletEnvironment和MockEnvironment、StandardReactiveWebEnvironment4种实现,分别代表普通程序、Web程序、测试程序的环境、响应式web环境。通过上述的getOrCreateEnvironment方法处理逻辑也是可以总结出来的。

StandardReactiveWebEnvironment是Springboot2新引入的,之前的版本没有这个类。关于这一个后续的章节会单独的详细讲解。

2.环境的装载

在上面的代码中实例化了StandardServletEnvironment类(我自己的环境是web),实例化该类的时候肯定会实例化其父类AbstractEnvironment,AbstractEnvironment类的构造函数如下:

public AbstractEnvironment() {

    customizePropertySources(this.propertySources);

 }

需要注意一点,因为实例化的是StandardServletEnvironment类,jvm会自动触发其父类中的构造函数,但是当前程序的this指针依然是StandardServletEnvironment。

this.propertySources属性如下所示:

AbstractEnvironment.java

private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);

我们继续跟踪customizePropertySources方法,如下所示:

AbstractEnvironment.java

protected void customizePropertySources(MutablePropertySources propertySources) {

 }

好吧,customizePropertySources方法竟然是个空的实现,但是注意一点,当前程序this是StandardServletEnvironment实例,我们不妨看一下StandardServletEnvironment类中是否重写了该方法。果不其然,StandardServletEnvironment类重写了customizePropertySources方法,详细代码如下所示:

StandardServletEnvironment.java

protected void customizePropertySources(MutablePropertySources propertySources) {

 //servletConfigInitParams

propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));

//servletContextInitParams

propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));

 //jndiProperties

 if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {

     propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));

    }

    super.customizePropertySources(propertySources);

    }

上述的代码中,propertySources为AbstractEnvironment.java中的propertySources字段,因为他是个引用类型,所以可以拿到指针即可修改其值。

1.4. propertySources类

虽然我们暂时还不知道propertySources要干啥,但是我们还是先看明白PropertySources到底要干啥。PropertySources类可以参考springboot源码分析6-springboot之PropertySource类初探一文。

我们再次看一下customizePropertySources方法的实现:

首先添加servletConfigInitParams,然后添加servletContextInitParams,其次判断是否是jndi环境,如果是则添加jndiProperties,最后调用父类的customizePropertySources(propertySources)。

在跟进父类的customizePropertySources(propertySources)方法之前,我们总结一下MutablePropertySources类中propertySourceList已经存在的属性为servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)。

StandardEnvironment类为StandardServletEnvironment类的父类,该类的customizePropertySources方法如下:

protected void customizePropertySources(MutablePropertySources propertySources) {

    propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));

    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));

  }

1、添加systemProperties

2、添加systemEnvironment。

上述的方法逻辑执行完毕之后,MutablePropertySources类中propertySourceList已经存在的属性为servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment。

经过一系列的跟踪getOrCreateEnvironment方法所做的事情已经分析完毕了。我们不妨继往下看。

3.配置环境信息

configureEnvironment(environment, applicationArguments.getSourceArgs())方法详细实现如下所示:

protected void configureEnvironment(ConfigurableEnvironment environment,

    String[] args) {

    configurePropertySources(environment, args);

    configureProfiles(environment, args);

  }

3.1配置属性源

configurePropertySources(environment, args)方法的核心实现如下:

protected void configurePropertySources(ConfigurableEnvironment environment,

    String[] args) {

    MutablePropertySources sources = environment.getPropertySources();

    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {

       sources.addLast(

       new MapPropertySource("defaultProperties", this.defaultProperties));

    }

    if (this.addCommandLineProperties && args.length > 0) {

       String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;

       if (sources.contains(name)) {

           PropertySource<?> source = sources.get(name);

           CompositePropertySource composite = new CompositePropertySource(name);

               composite.addPropertySource(new SimpleCommandLinePropertySource(

           name + "-" + args.hashCode(), args));

           composite.addPropertySource(source);

       sources.replace(name, composite);

       }

    else {

           sources.addFirst(new SimpleCommandLinePropertySource(args));

    }

    }

     }

1、如果defaultProperties不为空,则继续添加defaultProperties。思考一个问题defaultProperties怎么设置?

2、如果addCommandLineProperties为true并且有命令参数,分两步骤走:第一步存在commandLineArgs则继续设置属性;第二步commandLineArgs不存在则在头部添加commandLineArgs。

上述的代码执行完毕之后,MutablePropertySources类中propertySourceList已经存在的属性为commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)。

3.2配置Profiles

这个后续我们用到了再来讲解。

本文我们暂时讲解到这里,后续的文章中,我们继续跟踪属性文件的加载规则以及加载过程。提前曝光一点:

commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)中的属性优先级从前到后依次降低。在最前面的使用优先级最高。

比如commandLineArgs中存在一个属性a=1; systemProperties中存在一个属性a=2,则我们程序使用的时候a=1,因为越靠前的优先级越高。通过上述的优先级我们可以发现一个规律,命令行的优先级最高、其次是程序中的、然后是系统的环境变量以及属性、最后是默认的。

propertySources接口我们下一节课重点进行分析。


欢迎关注我的微信公众号,第一时间获得博客更新提醒,以及更多成体系的Java相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。 

时间: 2024-09-12 22:50:42

springboot源码分析7-环境属性构造过程(上)的相关文章

springboot源码分析6-springboot之PropertySource类初探

摘要:本小节重点梳理一下PropertySource类的相关结构以及职责,本文的学习前提是学习了springboot源码分析5-springboot之命令行参数以及原理一文. 在springboot源码分析5-springboot之命令行参数以及原理一文中,我们看到了实例化Source类的时候,会去先实例化其父类SimpleCommandLinePropertySource.SimpleCommandLinePropertySource类的构造函数中直接解析了命令行参数以及值,然后返回封装好的C

springboot源码分析3-springboot之banner类架构以及原理

继续上文的<<springboot源码分析2-springboot 之banner定制以及原理章节>>进行讲解,上一节我们详细详解了banner的三种输出模式.banner的输出模式设置.banner类的架构.SpringApplicationBannerPrinter类.ImageBanner以及TextBanner的处理方式.本小节我们来重点讲解一下各种banner处理类的相关实现逻辑以及设计意图和职责. 1.1 SpringBootBanner类 SpringBootBann

springboot源码分析11-ApplicationContextInitializer原理

摘要:springboot源码分析10-ApplicationContextInitializer使用一文中,我们详细地讲解了ApplicationContextInitializer的三种使用方式,本文我们重点看一下为何这三种方式都可以使用,也就是框架是如何处理的.包括内置的ContextIdApplicationContextInitializer.DelegatingApplicationContextInitializer. 1.1. 用户手动添加ApplicationContextIn

springboot源码分析10-ApplicationContextInitializer使用

摘要:spring中ApplicationContextInitializer接口是在ConfigurableApplicationContext刷新之前初始化ConfigurableApplicationContext的回调接口.当spring框架内部执行 ConfigurableApplicationContext#refresh() 方法的时候回去回调. 1.1. 实现方式一 首先,我们需要自定义一个类并且实现ApplicationContextInitializer接口.示例代码如下:

springboot源码分析2-springboot 之banner定制以及原理

1. springboot源码分析2-springboot 之banner定制以及原理 springboot在启动的时候,默认会在控制台输出默认的banner.也就是我们经常所说的图案,输出的图案如下所示:   .   ____          _            __ _ _  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \  \\/  ___)| |_)| | | |

Hive源码分析:Driver类运行过程

说明: 本文的源码分析基于hive-0.12.0-cdh5.0.1. 概括 从<hive cli的入口类>中可以知道hive中处理hive命令的处理器一共有以下几种: (1)set SetProcessor,设置修改参数,设置到SessionState的HiveConf里. (2)dfs DfsProcessor,使用hadoop的FsShell运行hadoop的命令. (3)add AddResourceProcessor,添加到SessionState的resource_map里,运行提交

springboot源码分析14-ApplicationContextInitializer原理Springboot中PropertySource注解多环境支持以及原理

摘要:Springboot中PropertySource注解的使用一文中,详细讲解了PropertySource注解的使用,通过PropertySource注解去加载指定的资源文件.然后将加载的属性注入到指定的配置类,@value以及@ConfigurationProperties的使用.但是也遗留一个问题,PropertySource注解貌似是不支持多种环境的动态切换?这个问题该如何解决呢?我们需要从源码中看看他到底是否支持. 首先,我们开始回顾一下上节课说的PropertySource注解的

springboot源码分析9-random的使用以及原理

摘要:springboot框架为我们提供了很多的便利,其中有一个非常有意思的功能,那就是可以通过变量的方式来配置一个随机数random,然后使用random随机出各式各样数值.本位重点讲解一下random的使用以及框架内部的实现机制. 1.1. Springboot中random的使用 首先我们定义一个配置类,如下所示: 1 @Component 2 public class Config { 3  @Value("${random.value}") 4  private String

springboot源码分析4-springboot之SpringFactoriesLoader使用

摘要:本文我们重点分析一下Spring框架中的SpringFactoriesLoader类以及META-INF/spring.factories的使用.在详细分析之前,我们可以思考一个问题?在我们设计一套API供别人调用的时候,如果同一个功能的要求特别多,或者同一个接口要面对很复杂的业务场景,这个时候我们该怎么办呢?其一:我们可以规范不同的系统调用,也就是传递一个系统标识:其二:我们在内部编码的时候可以使用不同的条件判断语句进行处理:其三:我们可以写几个策略类来来应对这个复杂的业务逻辑,比如同一